diff --git a/README.rst b/README.rst index 3bdf7682..66f7523e 100644 --- a/README.rst +++ b/README.rst @@ -214,6 +214,16 @@ Building ImageMagick with heic support: `Building ImageMagick with heic support` .. _`Building ImageMagick with heic support`: doc/build_im_with_heic_support.rst +EPS support +~~~~~~~~~~~~ + +You need to edit the policies of ImageMagick in /etc/ImageMagick-*/policy.xml. + +.. code:: xhtml + + + +Just wrap it between to comment it. ----- Usage diff --git a/doc/supported_mimetypes.rst b/doc/supported_mimetypes.rst index 786d3e30..3227eed5 100644 --- a/doc/supported_mimetypes.rst +++ b/doc/supported_mimetypes.rst @@ -11,14 +11,6 @@ +-------------------------------------------------------------------------+------------------------------------------+ |application/sketch |.sketch | +-------------------------------------------------------------------------+------------------------------------------+ - |**Images generator from Drawio files** | 120| - +-------------------------------------------------------------------------+------------------------------------------+ - |application/drawio |.drawio | - +-------------------------------------------------------------------------+------------------------------------------+ - |**application/vnd.scribus - based on Scribus** | 110| - +-------------------------------------------------------------------------+------------------------------------------+ - |application/vnd.scribus | - | - +-------------------------------------------------------------------------+------------------------------------------+ |**Archive files** | 100| +-------------------------------------------------------------------------+------------------------------------------+ |application/x-compressed | - | @@ -411,11 +403,11 @@ +-------------------------------------------------------------------------+------------------------------------------+ |image/x-xpixmap |.xpm | +-------------------------------------------------------------------------+------------------------------------------+ - |**Images - based on convert command (Image magick)** | 30| + |**Images - based on WAND (image magick)** | 30| +-------------------------------------------------------------------------+------------------------------------------+ - |image/x-jg |.art | + |application/postscript |.ai, .eps, .ps, .epsi, .epsf, .eps2, .eps3| +-------------------------------------------------------------------------+------------------------------------------+ - |image/x-sony-arw |.arw | + |image/x-jg |.art | +-------------------------------------------------------------------------+------------------------------------------+ |image/x-ms-bmp |.bmp | +-------------------------------------------------------------------------+------------------------------------------+ @@ -425,12 +417,10 @@ +-------------------------------------------------------------------------+------------------------------------------+ |application/dicom |.dcm | +-------------------------------------------------------------------------+------------------------------------------+ - |image/x-kodak-dcr |.dcr | + |application/x-director |.dcr, .dir, .dxr | +-------------------------------------------------------------------------+------------------------------------------+ |image/vnd.djvu |.djvu, .djv | +-------------------------------------------------------------------------+------------------------------------------+ - |image/x-adobe-dng |.dng | - +-------------------------------------------------------------------------+------------------------------------------+ |application/msword |.doc, .dot, .wiz | +-------------------------------------------------------------------------+------------------------------------------+ |image/x-epson-erf |.erf | @@ -447,12 +437,6 @@ +-------------------------------------------------------------------------+------------------------------------------+ |application/json |.json | +-------------------------------------------------------------------------+------------------------------------------+ - |image/x-kodak-k25 |.k25 | - +-------------------------------------------------------------------------+------------------------------------------+ - |image/x-kodak-kdc |.kdc | - +-------------------------------------------------------------------------+------------------------------------------+ - |image/x-minolta-mrw |.mrw | - +-------------------------------------------------------------------------+------------------------------------------+ |image/x-nikon-nef |.nef | +-------------------------------------------------------------------------+------------------------------------------+ |image/x-olympus-orf |.orf | @@ -463,8 +447,6 @@ +-------------------------------------------------------------------------+------------------------------------------+ |image/pcx |.pcx | +-------------------------------------------------------------------------+------------------------------------------+ - |image/x-pentax-pef |.pef | - +-------------------------------------------------------------------------+------------------------------------------+ |application/x-font |.pfa, .pfb, .gsf | +-------------------------------------------------------------------------+------------------------------------------+ |image/x-portable-graymap |.pgm | @@ -477,20 +459,10 @@ +-------------------------------------------------------------------------+------------------------------------------+ |image/x-photoshop |.psd | +-------------------------------------------------------------------------+------------------------------------------+ - |image/x-fuji-raf |.raf | - +-------------------------------------------------------------------------+------------------------------------------+ |image/x-cmu-raster |.ras | +-------------------------------------------------------------------------+------------------------------------------+ - |image/x-panasonic-raw |.raw | - +-------------------------------------------------------------------------+------------------------------------------+ |image/x-rgb |.rgb | +-------------------------------------------------------------------------+------------------------------------------+ - |image/x-panasonic-rw2 |.rw2 | - +-------------------------------------------------------------------------+------------------------------------------+ - |image/x-sony-sr2 |.sr2 | - +-------------------------------------------------------------------------+------------------------------------------+ - |image/x-sony-srf |.srf | - +-------------------------------------------------------------------------+------------------------------------------+ |image/tiff |.tif, .tiff | +-------------------------------------------------------------------------+------------------------------------------+ |application/vnd.visio |.vsd, .vst, .vsw, .vss | @@ -499,8 +471,6 @@ +-------------------------------------------------------------------------+------------------------------------------+ |application/x-ms-wmz |.wmz | +-------------------------------------------------------------------------+------------------------------------------+ - |image/x-sigma-x3f |.x3f | - +-------------------------------------------------------------------------+------------------------------------------+ |image/x-xbitmap |.xbm | +-------------------------------------------------------------------------+------------------------------------------+ |application/x-xcf |.xcf | @@ -509,51 +479,11 @@ +-------------------------------------------------------------------------+------------------------------------------+ |image/x-xwindowdump |.xwd | +-------------------------------------------------------------------------+------------------------------------------+ - |image/x-sony-arw |.arw | - +-------------------------------------------------------------------------+------------------------------------------+ - |image/x-adobe-dng |.dng | - +-------------------------------------------------------------------------+------------------------------------------+ - |image/x-sony-sr2 |.sr2 | - +-------------------------------------------------------------------------+------------------------------------------+ - |image/x-sony-srf |.srf | - +-------------------------------------------------------------------------+------------------------------------------+ - |image/x-sigma-x3f |.x3f | - +-------------------------------------------------------------------------+------------------------------------------+ - |image/x-canon-crw |.crw | - +-------------------------------------------------------------------------+------------------------------------------+ - |image/x-canon-cr2 |.cr2 | - +-------------------------------------------------------------------------+------------------------------------------+ - |image/x-epson-erf |.erf | - +-------------------------------------------------------------------------+------------------------------------------+ - |image/x-fuji-raf |.raf | - +-------------------------------------------------------------------------+------------------------------------------+ - |image/x-nikon-nef |.nef | - +-------------------------------------------------------------------------+------------------------------------------+ - |image/x-olympus-orf |.orf | - +-------------------------------------------------------------------------+------------------------------------------+ - |image/x-panasonic-raw |.raw | - +-------------------------------------------------------------------------+------------------------------------------+ - |image/x-panasonic-rw2 |.rw2 | - +-------------------------------------------------------------------------+------------------------------------------+ - |image/x-pentax-pef |.pef | - +-------------------------------------------------------------------------+------------------------------------------+ - |image/x-kodak-dcr |.dcr | - +-------------------------------------------------------------------------+------------------------------------------+ - |image/x-kodak-k25 |.k25 | - +-------------------------------------------------------------------------+------------------------------------------+ - |image/x-kodak-kdc |.kdc | - +-------------------------------------------------------------------------+------------------------------------------+ - |image/x-minolta-mrw |.mrw | - +-------------------------------------------------------------------------+------------------------------------------+ |application/x-xcf |.xcf | +-------------------------------------------------------------------------+------------------------------------------+ |image/x-xcf | - | +-------------------------------------------------------------------------+------------------------------------------+ - |**Bitmap images - based on Pillow** | 20| - +-------------------------------------------------------------------------+------------------------------------------+ - |image/png |.png | - +-------------------------------------------------------------------------+------------------------------------------+ - |application/postscript |.ai, .eps, .ps, .epsi, .epsf, .eps2, .eps3| + |application/x-xcf |.xcf | +-------------------------------------------------------------------------+------------------------------------------+ - |image/x-eps | - | + |image/x-xcf | - | +-------------------------------------------------------------------------+------------------------------------------+ diff --git a/preview_generator/preview/builder/cad__vtk.py b/preview_generator/preview/builder/cad__vtk.py index 15c234b5..fa9da323 100644 --- a/preview_generator/preview/builder/cad__vtk.py +++ b/preview_generator/preview/builder/cad__vtk.py @@ -6,7 +6,7 @@ from preview_generator.exception import BuilderDependencyNotFound from preview_generator.exception import UnsupportedMimeType from preview_generator.extension import mimetypes_storage -from preview_generator.preview.builder.image__pillow import ImagePreviewBuilderPillow # nopep8 +from preview_generator.preview.builder.image__wand import ImagePreviewBuilderWand # nopep8 from preview_generator.preview.generic_preview import PreviewBuilder from preview_generator.utils import ImgDims from preview_generator.utils import MimetypeMapping @@ -188,7 +188,7 @@ def build_jpeg_preview( writer.SetInputConnection(windowto_image_filter.GetOutputPort()) writer.Write() - return ImagePreviewBuilderPillow().build_jpeg_preview( + return ImagePreviewBuilderWand().build_jpeg_preview( tmp_png.name, preview_name, cache_path, page_id, extension, size, mimetype ) diff --git a/preview_generator/preview/builder/document__drawio.py b/preview_generator/preview/builder/document__drawio.py index 18236db8..eedd4d58 100644 --- a/preview_generator/preview/builder/document__drawio.py +++ b/preview_generator/preview/builder/document__drawio.py @@ -7,7 +7,7 @@ from preview_generator.exception import BuilderDependencyNotFound from preview_generator.exception import IntermediateFileBuildingFailed -from preview_generator.preview.builder.image__pillow import ImagePreviewBuilderPillow +from preview_generator.preview.builder.image__wand import ImagePreviewBuilderWand from preview_generator.preview.generic_preview import PreviewBuilder from preview_generator.utils import ImgDims from preview_generator.utils import MimetypeMapping @@ -90,7 +90,7 @@ def build_jpeg_preview( "failed with status {}".format(build_jpg_result_code) ) - ImagePreviewBuilderPillow().build_jpeg_preview( + ImagePreviewBuilderWand().build_jpeg_preview( tmp_jpg.name, preview_name, cache_path, page_id, extension, size, mimetype ) diff --git a/preview_generator/preview/builder/document__sketch.py b/preview_generator/preview/builder/document__sketch.py index ed14030c..fcc5a3b1 100644 --- a/preview_generator/preview/builder/document__sketch.py +++ b/preview_generator/preview/builder/document__sketch.py @@ -3,7 +3,7 @@ import typing import zipfile -from preview_generator.preview.builder.image__pillow import ImagePreviewBuilderPillow # nopep8 +from preview_generator.preview.builder.image__wand import ImagePreviewBuilderWand # nopep8 from preview_generator.preview.generic_preview import PreviewBuilder from preview_generator.utils import ImgDims from preview_generator.utils import MimetypeMapping @@ -46,7 +46,7 @@ def build_jpeg_preview( zip.extract("previews/preview.png", tmp_dir) zip.close() - ImagePreviewBuilderPillow().build_jpeg_preview( + ImagePreviewBuilderWand().build_jpeg_preview( tmp_dir + "/previews/preview.png", preview_name, cache_path, diff --git a/preview_generator/preview/builder/image__cairosvg.py b/preview_generator/preview/builder/image__cairosvg.py index cd6be854..bd420ecf 100644 --- a/preview_generator/preview/builder/image__cairosvg.py +++ b/preview_generator/preview/builder/image__cairosvg.py @@ -5,7 +5,7 @@ # HACK - G.M - 2020-12-26 - Hack to allow loading modules without cairosvg installed from preview_generator.exception import BuilderDependencyNotFound -from preview_generator.preview.builder.image__pillow import ImagePreviewBuilderPillow # nopep8 +from preview_generator.preview.builder.image__wand import ImagePreviewBuilderWand # nopep8 from preview_generator.preview.generic_preview import ImagePreviewBuilder from preview_generator.utils import ImgDims @@ -55,7 +55,7 @@ def build_jpeg_preview( ) as tmp_png: cairosvg.svg2png(url=file_path, write_to=tmp_png.name, dpi=96) - return ImagePreviewBuilderPillow().build_jpeg_preview( + return ImagePreviewBuilderWand().build_jpeg_preview( tmp_png.name, preview_name, cache_path, page_id, extension, size, mimetype ) diff --git a/preview_generator/preview/builder/image__imconvert.py b/preview_generator/preview/builder/image__imconvert.py index e1b418c4..e6d6d384 100644 --- a/preview_generator/preview/builder/image__imconvert.py +++ b/preview_generator/preview/builder/image__imconvert.py @@ -21,7 +21,8 @@ class ImagePreviewBuilderIMConvert(ImagePreviewBuilder): - """IM means Image Magick""" + """WARNING : This builder is deprecated, prefer ImagePreviewBuilderWand instead which + support the same list of format.""" MIMETYPES = [] # type: typing.List[str] # TODO - G.M - 2019-11-21 - find better storage solution for mimetype mapping @@ -53,7 +54,7 @@ class ImagePreviewBuilderIMConvert(ImagePreviewBuilder): MimetypeMapping("image/heic", ".heif"), ] - weight = 30 + weight = 0 @classmethod def get_label(cls) -> str: diff --git a/preview_generator/preview/builder/image__inkscape.py b/preview_generator/preview/builder/image__inkscape.py index 3360f847..f212a85d 100644 --- a/preview_generator/preview/builder/image__inkscape.py +++ b/preview_generator/preview/builder/image__inkscape.py @@ -10,7 +10,7 @@ from preview_generator.exception import BuilderDependencyNotFound from preview_generator.exception import IntermediateFileBuildingFailed -from preview_generator.preview.builder.image__pillow import ImagePreviewBuilderPillow # nopep8 +from preview_generator.preview.builder.image__wand import ImagePreviewBuilderWand # nopep8 from preview_generator.preview.generic_preview import ImagePreviewBuilder from preview_generator.utils import ImgDims from preview_generator.utils import executable_is_available @@ -83,6 +83,6 @@ def build_jpeg_preview( "failed with status {}".format(build_png_result_code) ) - return ImagePreviewBuilderPillow().build_jpeg_preview( + return ImagePreviewBuilderWand().build_jpeg_preview( tmp_png.name, preview_name, cache_path, page_id, extension, size, mimetype ) diff --git a/preview_generator/preview/builder/image__pillow.py b/preview_generator/preview/builder/image__pillow.py index aff657f0..8434174d 100644 --- a/preview_generator/preview/builder/image__pillow.py +++ b/preview_generator/preview/builder/image__pillow.py @@ -210,6 +210,9 @@ def get_strategy(self, image: PIL.Image) -> ImageConvertStrategy: class ImagePreviewBuilderPillow(ImagePreviewBuilder): + """WARNING : This builder is deprecated, prefer ImagePreviewBuilderWand instead which + support the same list of format.""" + weight = 20 def __init__( diff --git a/preview_generator/preview/builder/image__wand.py b/preview_generator/preview/builder/image__wand.py index 4187ebc2..ce0403e6 100644 --- a/preview_generator/preview/builder/image__wand.py +++ b/preview_generator/preview/builder/image__wand.py @@ -1,27 +1,64 @@ # -*- coding: utf-8 -*- - -from io import BytesIO +import os import typing -from wand.image import Color -from wand.image import Image as WImage +from wand.color import Color +from wand.image import Image +from wand.exceptions import CoderError, CoderFatalError, CoderWarning import wand.version -from preview_generator.preview.builder.image__imconvert import ImagePreviewBuilderIMConvert +from preview_generator.extension import mimetypes_storage +from preview_generator.exception import BuilderDependencyNotFound from preview_generator.preview.generic_preview import ImagePreviewBuilder from preview_generator.utils import ImgDims +from preview_generator.utils import MimetypeMapping from preview_generator.utils import compute_resize_dims +from preview_generator.utils import executable_is_available from preview_generator.utils import imagemagick_supported_mimes +DEFAULT_JPEG_QUALITY = 85 +DEFAULT_JPEG_PROGRESSIVE = True + class ImagePreviewBuilderWand(ImagePreviewBuilder): - """ - WARNING : This builder is deprecated, prefer ImagePreviewBuilderIMConvert instead which - support the same list of format. - """ - weight = 10 + weight = 30 MIMETYPES = [] # type: typing.List[str] + # TODO - G.M - 2019-11-21 - find better storage solution for mimetype mapping + # dict and/or list. + # see https://github.com/algoo/preview-generator/pull/148#discussion_r346381508 + SUPPORTED_RAW_CAMERA_MIMETYPE_MAPPING = [ + MimetypeMapping("image/x-sony-arw", ".arw"), + MimetypeMapping("image/x-adobe-dng", ".dng"), + MimetypeMapping("image/x-sony-sr2", ".sr2"), + MimetypeMapping("image/x-sony-srf", ".srf"), + MimetypeMapping("image/x-sigma-x3f", ".x3f"), + MimetypeMapping("image/x-canon-crw", ".crw"), + MimetypeMapping("image/x-canon-cr2", ".cr2"), + MimetypeMapping("image/x-epson-erf", ".erf"), + MimetypeMapping("image/x-fuji-raf", ".raf"), + MimetypeMapping("image/x-nikon-nef", ".nef"), + MimetypeMapping("image/x-olympus-orf", ".orf"), + MimetypeMapping("image/x-panasonic-raw", ".raw"), + MimetypeMapping("image/x-panasonic-rw2", ".rw2"), + MimetypeMapping("image/x-pentax-pef", ".pef"), + MimetypeMapping("image/x-kodak-dcr", ".dcr"), + MimetypeMapping("image/x-kodak-k25", ".k25"), + MimetypeMapping("image/x-kodak-kdc", ".kdc"), + MimetypeMapping("image/x-minolta-mrw", ".mrw"), + ] + + SUPPORTED_HEIC_MIMETYPE_MAPPING = [ + MimetypeMapping("image/heic", ".heic"), + MimetypeMapping("image/heic", ".heif"), + ] + + def __init__( + self, quality: int = DEFAULT_JPEG_QUALITY, progressive: bool = DEFAULT_JPEG_PROGRESSIVE, + ): + super().__init__() + self.quality = quality + self.progressive = progressive @classmethod def get_label(cls) -> str: @@ -37,7 +74,29 @@ def __load_mimetypes(cls) -> typing.List[str]: Load supported mimetypes from WAND library :return: list of supported mime types """ - return imagemagick_supported_mimes() + + mimes = imagemagick_supported_mimes() # type: typing.List[str] + # HACK - G.M - 2019-10-31 - Handle raw format only if ufraw-batch is installed as most common + # default imagemagick configuration delegate raw format to ufraw-batch. + if executable_is_available("ufraw-batch"): + for mimetype_mapping in cls.SUPPORTED_RAW_CAMERA_MIMETYPE_MAPPING: + mimes.append(mimetype_mapping.mimetype) + return mimes + + @classmethod + def get_mimetypes_mapping(cls) -> typing.List[MimetypeMapping]: + mimetypes_mapping = [] # type: typing.List[MimetypeMapping] + mimetypes_mapping = ( + mimetypes_mapping + + cls.SUPPORTED_RAW_CAMERA_MIMETYPE_MAPPING + + cls.SUPPORTED_HEIC_MIMETYPE_MAPPING + ) + return mimetypes_mapping + + @classmethod + def check_dependencies(cls) -> None: + if not executable_is_available("convert"): + raise BuilderDependencyNotFound("this builder requires convert to be available") @classmethod def get_supported_mimetypes(cls) -> typing.List[str]: @@ -48,21 +107,9 @@ def get_supported_mimetypes(cls) -> typing.List[str]: ImagePreviewBuilderWand.MIMETYPES = cls.__load_mimetypes() mimetypes = ImagePreviewBuilderWand.MIMETYPES - # INFO - G.M - 2021-04-30 - # Disable support for postscript,xcf and raw image format in wand, to ensure - # proper builder is used (either imagemagick convert or pillow) - - invalid_mimetypes = ["application/postscript", "application/x-xcf", "image/x-xcf"] - for ( - mimetype_mapping - ) in ImagePreviewBuilderIMConvert().SUPPORTED_RAW_CAMERA_MIMETYPE_MAPPING: - invalid_mimetypes.append(mimetype_mapping.mimetype) - - for invalid_mimetype in invalid_mimetypes: - try: - mimetypes.remove(invalid_mimetype) - except ValueError: - pass + extra_mimetypes = ["application/x-xcf", "image/x-xcf"] + mimetypes.extend(extra_mimetypes) + return mimetypes def build_jpeg_preview( @@ -77,41 +124,47 @@ def build_jpeg_preview( ) -> None: if not size: size = self.default_size - with open(file_path, "rb") as img: - result = self.image_to_jpeg_wand(img, ImgDims(width=size.width, height=size.height)) - - with open( - "{path}{extension}".format(path=cache_path + preview_name, extension=extension), - "wb", - ) as jpeg: - buffer = result.read(1024) - while buffer: - jpeg.write(buffer) - buffer = result.read(1024) - - def image_to_jpeg_wand( - self, jpeg: typing.Union[str, typing.IO[bytes]], preview_dims: ImgDims - ) -> BytesIO: + preview_name = preview_name + extension + dest_path = os.path.join(cache_path, preview_name) + self.image_to_jpeg_wand(file_path, size, dest_path, mimetype=mimetype) + + def image_to_jpeg_wand(self, file_path: str, preview_dims: ImgDims, dest_path: str, + mimetype: typing.Optional[str]) -> None: + try: + with self._convert_image(file_path, preview_dims) as img: + img.save(filename=dest_path) + except (CoderError, CoderFatalError, CoderWarning) as e: + assert mimetype + file_ext = mimetypes_storage.guess_extension(mimetype, strict=False) or "" + if file_ext: + file_path = file_ext.lstrip(".") + ":" + file_path + with self._convert_image(file_path, preview_dims) as img: + img.save(filename=dest_path) + else: + raise e + + def _convert_image(self, file_path: str, preview_dims: ImgDims) -> Image: """ - for jpeg, gif and bmp - :param jpeg: - :param size: - :return: + refer: https://legacy.imagemagick.org/Usage/thumbnails/ + like cmd: convert -layers merge -background white -thumbnail widthxheight \ + -auto-orient -quality 85 -interlace plane input.jpeg output.jpeg """ - self.logger.info("Converting image to jpeg using wand") - - with WImage(file=jpeg, background=Color("white")) as image: - - preview_dims = ImgDims(width=preview_dims.width, height=preview_dims.height) - - resize_dim = compute_resize_dims( - dims_in=ImgDims(width=image.size[0], height=image.size[1]), dims_out=preview_dims - ) - image.resize(resize_dim.width, resize_dim.height) - # INFO - jumenzel - 2019-03-12 - remove metadata, color-profiles from this image. - image.strip() - content_as_bytes = image.make_blob("jpeg") - output = BytesIO() - output.write(content_as_bytes) - output.seek(0, 0) - return output + + img = Image(filename=file_path) + resize_dim = compute_resize_dims( + dims_in=ImgDims(width=img.width, height=img.height), dims_out=preview_dims + ) + + img.auto_orient() + img.iterator_reset() + img.background_color = Color("white") + img.merge_layers("merge") + + if self.progressive: + img.interlace_scheme = "plane" + + img.compression_quality = self.quality + + img.thumbnail(resize_dim.width, resize_dim.height) + + return img diff --git a/preview_generator/preview/builder_factory.py b/preview_generator/preview/builder_factory.py index e6e92293..acf0f9ae 100644 --- a/preview_generator/preview/builder_factory.py +++ b/preview_generator/preview/builder_factory.py @@ -108,9 +108,10 @@ def load_builders(self, force: bool = False) -> None: if is_abstract(cls): # INFO - G.M - 2021-06-22 - Skip abstract classes from loaded builders pass - elif cls.__name__ == "ImagePreviewBuilderWand": + elif cls.__name__ in ("ImagePreviewBuilderPillow", "ImagePreviewBuilderIMConvert"): self.logger.info( - "ImagePreviewBuilderWand builder is deprecated and is not registered by default. Consider using ImagePreviewBuilderIMConvert instead" + "%s builder is deprecated and is not registered by default. " + "Consider using ImagePreviewBuilderIMConvert instead".format(cls.__name__) ) else: self.register_builder(cls, overwrite=False) diff --git a/tests/builders/test_image_wand_builder.py b/tests/builders/test_image_wand_builder.py new file mode 100644 index 00000000..0bac49db --- /dev/null +++ b/tests/builders/test_image_wand_builder.py @@ -0,0 +1,33 @@ +import os +from PIL import Image +from preview_generator.preview.builder.image__wand import ImagePreviewBuilderWand +from preview_generator.preview.builder.image__imconvert import ImagePreviewBuilderIMConvert +from preview_generator.utils import ImgDims + +CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) +CACHE_DIR = "/tmp/preview-generator-tests/cache" + + +def test_build_jpeg_preview() -> None: + wand_builder = ImagePreviewBuilderWand() + test_orient_path = os.path.join(CURRENT_DIR, "the_img.png") + extension = ".jpg" + preview_name = "preview_the_img" + width = 512 + height = 256 + size = ImgDims(width=width, height=height) + wand_builder.build_jpeg_preview( + file_path=test_orient_path, + preview_name=preview_name, + cache_path=CACHE_DIR, + page_id=-1, + size=size, + extension=extension + ) + preview_name = preview_name + extension + dest_path = os.path.join(CACHE_DIR, preview_name) + assert os.path.exists(dest_path) + assert os.path.getsize(dest_path) > 0 + with Image.open(dest_path) as jpg: + assert jpg.height == height + assert jpg.width in range(288, 290) diff --git a/tests/builders/the_img.png b/tests/builders/the_img.png new file mode 100644 index 00000000..c85b50ca Binary files /dev/null and b/tests/builders/the_img.png differ diff --git a/tests/input/arw_raw/DSC08523 b/tests/input/arw_raw/DSC08523 new file mode 100644 index 00000000..e1ec9965 Binary files /dev/null and b/tests/input/arw_raw/DSC08523 differ diff --git a/tests/input/arw_raw/test_arw_raw.py b/tests/input/arw_raw/test_arw_raw.py index f3f9c418..c94c18e6 100644 --- a/tests/input/arw_raw/test_arw_raw.py +++ b/tests/input/arw_raw/test_arw_raw.py @@ -17,6 +17,7 @@ CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) CACHE_DIR = "/tmp/preview-generator-tests/cache" IMAGE_FILE_PATH = os.path.join(CURRENT_DIR, "DSC08523.ARW") +IMAGE_FILE_PATH_NO_EXTENSION = os.path.join(CURRENT_DIR, "DSC08523") if not executable_is_available("ufraw-batch"): pytest.skip("ufraw-batch is not available.", allow_module_level=True) @@ -42,6 +43,22 @@ def test_to_jpeg() -> None: assert jpeg.width in range(382, 384) +@pytest.mark.slow +def test_to_jpeg_no_extension() -> None: + manager = PreviewManager(cache_folder_path=CACHE_DIR, create_folder=True) + assert manager.has_jpeg_preview(file_path=IMAGE_FILE_PATH_NO_EXTENSION, file_ext=".ARW") is True + path_to_file = manager.get_jpeg_preview( + file_path=IMAGE_FILE_PATH_NO_EXTENSION, height=256, width=512, force=True, file_ext=".ARW" + ) + assert os.path.exists(path_to_file) is True + assert os.path.getsize(path_to_file) > 0 + assert re.match(test_utils.CACHE_FILE_PATH_PATTERN__JPEG, path_to_file) + + with Image.open(path_to_file) as jpeg: + assert jpeg.height == 256 + assert jpeg.width in range(382, 384) + + @pytest.mark.slow def test_get_nb_page() -> None: manager = PreviewManager(cache_folder_path=CACHE_DIR, create_folder=True) diff --git a/tests/input/heic/test_heic.py b/tests/input/heic/test_heic.py index 22723d17..4195d2ca 100644 --- a/tests/input/heic/test_heic.py +++ b/tests/input/heic/test_heic.py @@ -11,14 +11,14 @@ from preview_generator.exception import UnavailablePreviewType from preview_generator.manager import PreviewManager -from preview_generator.utils import imagemagick_supported_mimes from tests import test_utils CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) CACHE_DIR = "/tmp/preview-generator-tests/cache" IMAGE_FILE_PATH = os.path.join(CURRENT_DIR, "test_heic.heic") -if imagemagick_supported_mimes().count("image/heic") == 0: +manager = PreviewManager(cache_folder_path=CACHE_DIR, create_folder=True) +if "image/heic" not in manager.get_supported_mimetypes(): pytest.skip("heif-convert is not available.", allow_module_level=True) diff --git a/tests/input/transparent_img/__init__.py b/tests/input/transparent_img/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/input/transparent_img/test_transparent_img.py b/tests/input/transparent_img/test_transparent_img.py new file mode 100644 index 00000000..e15092f0 --- /dev/null +++ b/tests/input/transparent_img/test_transparent_img.py @@ -0,0 +1,69 @@ +import os +import re +import shutil +import typing + +from wand.image import Image +from wand.color import Color + +from preview_generator.manager import PreviewManager +from preview_generator.utils import compute_resize_dims, ImgDims +from tests import test_utils + +CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) +CACHE_DIR = "/tmp/preview-generator-tests/cache" + + +def setup_function(function: typing.Callable) -> None: + shutil.rmtree(CACHE_DIR, ignore_errors=True) + + +def test_gif_to_jpeg_with_background_white() -> None: + image_file_path = os.path.join(CURRENT_DIR, "the_gif.gif") + to_size = ImgDims(width=512, height=256) + with Image(filename=image_file_path) as input_img: + input_size = ImgDims(width=input_img.width, height=input_img.height) + expected_size = compute_resize_dims(input_size, to_size) + + manager = PreviewManager(cache_folder_path=CACHE_DIR, create_folder=True) + assert manager.has_jpeg_preview(file_path=image_file_path) is True + + path_to_file = manager.get_jpeg_preview( + file_path=image_file_path, width=to_size.width, height=to_size.height, force=True + ) + + assert os.path.exists(path_to_file) is True + assert os.path.getsize(path_to_file) > 0 + assert re.match(test_utils.CACHE_FILE_PATH_PATTERN__JPEG, path_to_file) + + with Image(filename=path_to_file) as output_img: + assert output_img.width == expected_size.width + assert output_img.height == expected_size.height + assert nearest_colour_white(output_img[1][1]) + + +def test_png_to_jpeg_with_background_white() -> None: + image_file_path = os.path.join(CURRENT_DIR, "the_png.png") + to_size = ImgDims(width=512, height=256) + with Image(filename=image_file_path) as input_img: + input_size = ImgDims(width=input_img.width, height=input_img.height) + expected_size = compute_resize_dims(input_size, to_size) + + manager = PreviewManager(cache_folder_path=CACHE_DIR, create_folder=True) + assert manager.has_jpeg_preview(file_path=image_file_path) is True + + path_to_file = manager.get_jpeg_preview( + file_path=image_file_path, width=to_size.width, height=to_size.height, force=True + ) + assert os.path.exists(path_to_file) is True + assert os.path.getsize(path_to_file) > 0 + assert re.match(test_utils.CACHE_FILE_PATH_PATTERN__JPEG, path_to_file) + + with Image(filename=path_to_file) as output_img: + assert output_img.width == expected_size.width + assert output_img.height == expected_size.height + assert nearest_colour_white(output_img[5][5]) + + +def nearest_colour_white(color: Color): + return color.red_int8 >= 250 and color.green_int8 >= 250 and color.blue_int8 >= 250 diff --git a/tests/input/transparent_img/the_gif.gif b/tests/input/transparent_img/the_gif.gif new file mode 100644 index 00000000..75313f67 Binary files /dev/null and b/tests/input/transparent_img/the_gif.gif differ diff --git a/tests/input/transparent_img/the_png.png b/tests/input/transparent_img/the_png.png new file mode 100644 index 00000000..80fb9729 Binary files /dev/null and b/tests/input/transparent_img/the_png.png differ