From 3ac346bdccfa743c4dd0d71d91214accfcde121dc8a46128f60fbb90ac53e40f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mark=C3=A9ta=20Machov=C3=A1?= Date: Thu, 14 Sep 2023 09:46:45 +0000 Subject: [PATCH] Accepting request 1111056 from home:mcalabkova:branches:devel:languages:python:numeric - Add patches for the compatibility with libavif 1.0.0: * libavif.patch * quantize.patch * avif.patch * tests.patch * integrate.patch OBS-URL: https://build.opensuse.org/request/show/1111056 OBS-URL: https://build.opensuse.org/package/show/devel:languages:python/python-imagecodecs?expand=0&rev=25 --- avif.patch | 177 +++++++++++++++++ integrate.patch | 77 ++++++++ libavif.patch | 387 +++++++++++++++++++++++++++++++++++++ python-imagecodecs.changes | 10 + python-imagecodecs.spec | 24 ++- quantize.patch | 233 ++++++++++++++++++++++ tests.patch | 199 +++++++++++++++++++ 7 files changed, 1100 insertions(+), 7 deletions(-) create mode 100644 avif.patch create mode 100644 integrate.patch create mode 100644 libavif.patch create mode 100644 quantize.patch create mode 100644 tests.patch diff --git a/avif.patch b/avif.patch new file mode 100644 index 0000000..c87d925 --- /dev/null +++ b/avif.patch @@ -0,0 +1,177 @@ +From 2f548c9a4df443948f2dfcde30a7211ce8b3adc2 Mon Sep 17 00:00:00 2001 +From: Christoph Gohlke +Date: Sat, 2 Sep 2023 22:41:40 -0700 +Subject: [PATCH] Update imagecodecs/_avif.pyx + +--- + imagecodecs/_avif.pyx | 60 +++++++++++++++++++++++-------------------- + 1 file changed, 32 insertions(+), 28 deletions(-) + +Index: imagecodecs-2023.3.16/imagecodecs/_avif.pyx +=================================================================== +--- imagecodecs-2023.3.16.orig/imagecodecs/_avif.pyx ++++ imagecodecs-2023.3.16/imagecodecs/_avif.pyx +@@ -56,12 +56,14 @@ class AVIF: + YUV422 = AVIF_PIXEL_FORMAT_YUV422 + YUV420 = AVIF_PIXEL_FORMAT_YUV420 + YUV400 = AVIF_PIXEL_FORMAT_YUV400 ++ COUNT = AVIF_PIXEL_FORMAT_COUNT + +- class QUANTIZER(enum.IntEnum): +- """AVIF codec quantizers.""" +- LOSSLESS = AVIF_QUANTIZER_LOSSLESS +- BEST_QUALITY = AVIF_QUANTIZER_BEST_QUALITY +- WORST_QUALITY = AVIF_QUANTIZER_WORST_QUALITY ++ class QUALITY(enum.IntEnum): ++ """AVIF codec quality.""" ++ DEFAULT = AVIF_QUALITY_DEFAULT # -1 ++ LOSSLESS = AVIF_QUALITY_LOSSLESS # 100 ++ WORST = AVIF_QUALITY_WORST # 0 ++ BEST = AVIF_QUALITY_BEST # 100 + + class SPEED(enum.IntEnum): + """AVIF codec speeds.""" +@@ -85,6 +87,7 @@ class AVIF: + LIBGAV1 = AVIF_CODEC_CHOICE_LIBGAV1 + RAV1E = AVIF_CODEC_CHOICE_RAV1E + SVT = AVIF_CODEC_CHOICE_SVT ++ AVM = AVIF_CODEC_CHOICE_AVM + + + class AvifError(RuntimeError): +@@ -143,8 +146,8 @@ def avif_encode( + const uint8_t[::1] dst # must be const to write to bytes + ssize_t dstsize, size + ssize_t itemsize = data.dtype.itemsize ++ int quality = AVIF_QUALITY_LOSSLESS + int speed_ = AVIF_SPEED_DEFAULT +- int quantizer = AVIF_QUANTIZER_LOSSLESS + int tilerowslog2 = 0 + int tilecolslog2 = 0 + int duration = 1 +@@ -172,10 +175,10 @@ def avif_encode( + src.dtype in (numpy.uint8, numpy.uint16) + # and numpy.PyArray_ISCONTIGUOUS(src) + and src.ndim in (2, 3, 4) +- and src.shape[0] < 2 ** 31 +- and src.shape[1] < 2 ** 31 +- and src.shape[src.ndim - 1] < 2 ** 31 +- and src.shape[src.ndim - 2] < 2 ** 31 ++ and src.shape[0] <= 2147483647 ++ and src.shape[1] <= 2147483647 ++ and src.shape[src.ndim - 1] <= 2147483647 ++ and src.shape[src.ndim - 2] <= 2147483647 + ): + raise ValueError('invalid data shape, strides, or dtype') + +@@ -205,10 +208,6 @@ def avif_encode( + monochrome = samples < 3 + hasalpha = samples in (2, 4) + +- if monochrome: +- raise NotImplementedError('cannot encode monochome images') +- # TODO: check status of libavif/aom monochome support +- + if bitspersample is None: + depth = itemsize * 8 + else: +@@ -223,11 +222,11 @@ def avif_encode( + if 0 <= tilecolslog2 <= 6: + raise ValueError('invalid tileColsLog2') + +- quantizer = _default_value( ++ quality = _default_value( + level, +- AVIF_QUANTIZER_LOSSLESS, +- AVIF_QUANTIZER_BEST_QUALITY, +- AVIF_QUANTIZER_WORST_QUALITY ++ AVIF_QUALITY_LOSSLESS, # 100 ++ AVIF_QUALITY_DEFAULT, # -1 ++ AVIF_QUALITY_BEST # 100 + ) + + speed_ = _default_value( +@@ -239,8 +238,8 @@ def avif_encode( + + if monochrome: + yuvformat = AVIF_PIXEL_FORMAT_YUV400 +- quantizer = AVIF_QUANTIZER_LOSSLESS +- elif quantizer == AVIF_QUANTIZER_LOSSLESS: ++ quality = AVIF_QUALITY_LOSSLESS ++ elif quality == AVIF_QUALITY_LOSSLESS: + yuvformat = AVIF_PIXEL_FORMAT_YUV444 + elif pixelformat is not None: + yuvformat = _avif_pixelformat(pixelformat) +@@ -254,11 +253,9 @@ def avif_encode( + if encoder == NULL: + raise AvifError('avifEncoderCreate', 'NULL') + ++ encoder.quality = quality ++ encoder.qualityAlpha = AVIF_QUALITY_LOSSLESS + encoder.maxThreads = maxthreads +- encoder.minQuantizer = quantizer +- encoder.maxQuantizer = quantizer +- encoder.minQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS +- encoder.maxQuantizerAlpha = AVIF_QUANTIZER_LOSSLESS + encoder.tileRowsLog2 = tilerowslog2 + encoder.tileColsLog2 = tilecolslog2 + encoder.speed = speed_ +@@ -269,7 +266,7 @@ def avif_encode( + if image == NULL: + raise AvifError('avifImageCreate', 'NULL') + +- if monochrome or quantizer == AVIF_QUANTIZER_LOSSLESS: ++ if monochrome or quality == AVIF_QUALITY_LOSSLESS: + if codecchoice == AVIF_CODEC_CHOICE_AUTO: + encoder.codecChoice = AVIF_CODEC_CHOICE_AOM + else: +@@ -287,7 +284,9 @@ def avif_encode( + + avifRGBImageSetDefaults(&rgb, image) + if monochrome: +- avifRGBImageAllocatePixels(&rgb) ++ res = avifRGBImageAllocatePixels(&rgb) ++ if res != AVIF_RESULT_OK: ++ raise AvifError('avifRGBImageAllocatePixels', res) + if rgb.format != AVIF_RGB_FORMAT_RGBA: + raise RuntimeError('rgb.format != AVIF_RGB_FORMAT_RGBA') + srcptr = src.data +@@ -419,7 +418,7 @@ def avif_encode( + return _return_output(out, dstsize, rawsize, outgiven) + + +-def avif_decode(data, index=None, out=None): ++def avif_decode(data, index=None, numthreads=None, out=None): + """Return decoded AVIF image.""" + cdef: + numpy.ndarray dst +@@ -429,6 +428,7 @@ def avif_decode(data, index=None, out=No + ssize_t samples, size, itemsize, i, j, k, dstindex, imagecount + bint monochrome = 0 # must be initialized + bint hasalpha = 0 ++ int maxthreads = _default_threads(numthreads) + uint8_t* dstptr = NULL + uint8_t* srcptr = NULL + avifDecoder* decoder = NULL +@@ -448,6 +448,8 @@ def avif_decode(data, index=None, out=No + # required to read AVIF files created by ImageMagick + decoder.strictFlags = AVIF_STRICT_DISABLED + ++ decoder.maxThreads = maxthreads ++ + res = avifDecoderSetSource(decoder, AVIF_DECODER_SOURCE_AUTO) + if res != AVIF_RESULT_OK: + raise AvifError('avifDecoderSetSource', res) +@@ -632,10 +634,12 @@ cdef _avif_codecchoice(codec): + AVIF_CODEC_CHOICE_LIBGAV1: AVIF_CODEC_CHOICE_LIBGAV1, + AVIF_CODEC_CHOICE_RAV1E: AVIF_CODEC_CHOICE_RAV1E, + AVIF_CODEC_CHOICE_SVT: AVIF_CODEC_CHOICE_SVT, ++ AVIF_CODEC_CHOICE_AVM: AVIF_CODEC_CHOICE_AVM, + 'auto': AVIF_CODEC_CHOICE_AUTO, + 'aom': AVIF_CODEC_CHOICE_AOM, + 'dav1d': AVIF_CODEC_CHOICE_DAV1D, + 'libgav1': AVIF_CODEC_CHOICE_LIBGAV1, + 'rav1e': AVIF_CODEC_CHOICE_RAV1E, + 'svt': AVIF_CODEC_CHOICE_SVT, ++ 'avm': AVIF_CODEC_CHOICE_AVM, + }[codec] diff --git a/integrate.patch b/integrate.patch new file mode 100644 index 0000000..71b55b5 --- /dev/null +++ b/integrate.patch @@ -0,0 +1,77 @@ +From e9b5a984b72c9d4e14f9d37ec99389d25645c7fb Mon Sep 17 00:00:00 2001 +From: Christoph Gohlke +Date: Sun, 3 Sep 2023 09:37:58 -0700 +Subject: [PATCH] Update imagecodecs/imagecodecs.py + +--- + imagecodecs/imagecodecs.py | 98 ++++++++++++++++++++++++-------------- + 1 file changed, 62 insertions(+), 36 deletions(-) + +Index: imagecodecs-2023.3.16/imagecodecs/imagecodecs.py +=================================================================== +--- imagecodecs-2023.3.16.orig/imagecodecs/imagecodecs.py ++++ imagecodecs-2023.3.16/imagecodecs/imagecodecs.py +@@ -483,9 +483,6 @@ import sys + import io + import importlib + import threading +- +-import numpy +- + from typing import TYPE_CHECKING + + if TYPE_CHECKING: +@@ -496,6 +493,8 @@ if TYPE_CHECKING: + + from numpy.typing import ArrayLike, NDArray + ++import numpy ++ + # map extension module names to attribute names + _MODULES: dict[str, list[str]] = { + '': [ +@@ -834,6 +833,14 @@ _MODULES: dict[str, list[str]] = { + 'pglz_check', + 'pglz_version', + ], ++ '_png': [ ++ 'PNG', ++ 'PngError', ++ 'png_encode', ++ 'png_decode', ++ 'png_check', ++ 'png_version', ++ ], + '_qoi': [ + 'QOI', + 'QoiError', +@@ -842,13 +849,13 @@ _MODULES: dict[str, list[str]] = { + 'qoi_check', + 'qoi_version', + ], +- '_png': [ +- 'PNG', +- 'PngError', +- 'png_encode', +- 'png_decode', +- 'png_check', +- 'png_version', ++ '_quantize': [ ++ 'QUANTIZE', ++ 'QuantizeError', ++ 'quantize_encode', ++ 'quantize_decode', ++ 'quantize_check', ++ 'quantize_version', + ], + '_rgbe': [ + 'RGBE', +@@ -1144,6 +1151,8 @@ def _stub(name: str, module: ModuleType + return StubError + + class StubType(type): ++ """Stub type metaclass.""" ++ + def __getattr__(cls, arg: str, /) -> Any: + raise DelayedImportError(name) + diff --git a/libavif.patch b/libavif.patch new file mode 100644 index 0000000..4f87873 --- /dev/null +++ b/libavif.patch @@ -0,0 +1,387 @@ +From d04112759c48772c4d46a2dfa4f4c6a76e23c9a9 Mon Sep 17 00:00:00 2001 +From: Christoph Gohlke +Date: Sat, 2 Sep 2023 16:52:31 -0700 +Subject: [PATCH] Update imagecodecs/libavif.pxd + +--- + imagecodecs/libavif.pxd | 159 ++++++++++++++++++++++++++-------------- + 1 file changed, 102 insertions(+), 57 deletions(-) + +diff --git a/imagecodecs/libavif.pxd b/imagecodecs/libavif.pxd +index 9d17cc5..c9db115 100644 +--- a/imagecodecs/libavif.pxd ++++ b/imagecodecs/libavif.pxd +@@ -1,10 +1,10 @@ + # imagecodecs/libavif.pxd + # cython: language_level = 3 + +-# Cython declarations for the `libavif 0.11.1` library. ++# Cython declarations for the `libavif 1.0.1` library. + # https://github.com/AOMediaCodec/libavif + +-from libc.stdint cimport uint8_t, uint32_t, uint64_t ++from libc.stdint cimport uint8_t, uint16_t, uint32_t, uint64_t, int32_t + + cdef extern from 'avif/avif.h': + +@@ -24,6 +24,11 @@ cdef extern from 'avif/avif.h': + int AVIF_DIAGNOSTICS_ERROR_BUFFER_SIZE + int AVIF_DEFAULT_IMAGE_COUNT_LIMIT + ++ int AVIF_QUALITY_DEFAULT ++ int AVIF_QUALITY_LOSSLESS ++ int AVIF_QUALITY_WORST ++ int AVIF_QUALITY_BEST ++ + int AVIF_QUANTIZER_LOSSLESS + int AVIF_QUANTIZER_BEST_QUALITY + int AVIF_QUANTIZER_WORST_QUALITY +@@ -34,6 +39,11 @@ cdef extern from 'avif/avif.h': + int AVIF_SPEED_SLOWEST + int AVIF_SPEED_FASTEST + ++ int AVIF_REPETITION_COUNT_INFINITE ++ int AVIF_REPETITION_COUNT_UNKNOWN ++ ++ int AVIF_MAX_AV1_LAYER_COUNT ++ + ctypedef enum avifPlanesFlag: + AVIF_PLANES_YUV + AVIF_PLANES_A +@@ -42,12 +52,10 @@ cdef extern from 'avif/avif.h': + ctypedef uint32_t avifPlanesFlags + + ctypedef enum avifChannelIndex: +- AVIF_CHAN_R +- AVIF_CHAN_G +- AVIF_CHAN_B + AVIF_CHAN_Y + AVIF_CHAN_U + AVIF_CHAN_V ++ AVIF_CHAN_A + + # Version + +@@ -82,7 +90,7 @@ cdef extern from 'avif/avif.h': + AVIF_RESULT_ENCODE_COLOR_FAILED + AVIF_RESULT_ENCODE_ALPHA_FAILED + AVIF_RESULT_BMFF_PARSE_FAILED +- AVIF_RESULT_NO_AV1_ITEMS_FOUND ++ AVIF_RESULT_MISSING_IMAGE_ITEM + AVIF_RESULT_DECODE_COLOR_FAILED + AVIF_RESULT_DECODE_ALPHA_FAILED + AVIF_RESULT_COLOR_ALPHA_SIZE_MISMATCH +@@ -101,6 +109,7 @@ cdef extern from 'avif/avif.h': + AVIF_RESULT_OUT_OF_MEMORY + AVIF_RESULT_CANNOT_CHANGE_SETTING + AVIF_RESULT_INCOMPATIBLE_IMAGE ++ AVIF_RESULT_NO_AV1_ITEMS_FOUND + + const char* avifResultToString( + avifResult result +@@ -118,12 +127,12 @@ cdef extern from 'avif/avif.h': + + # int AVIF_DATA_EMPTY { NULL, 0 } + +- void avifRWDataRealloc( ++ avifResult avifRWDataRealloc( + avifRWData* raw, + size_t newSize + ) nogil + +- void avifRWDataSet( ++ avifResult avifRWDataSet( + avifRWData* raw, + const uint8_t* data, + size_t len +@@ -133,6 +142,20 @@ cdef extern from 'avif/avif.h': + avifRWData* raw + ) nogil + ++ # Metadata ++ ++ avifResult avifGetExifTiffHeaderOffset( ++ const uint8_t* exif, ++ size_t exifSize, ++ size_t* offset ++ ) nogil ++ ++ avifResult avifGetExifOrientationOffset( ++ const uint8_t* exif, ++ size_t exifSize, ++ size_t* offset ++ ) nogil ++ + # avifPixelFormat + + ctypedef enum avifPixelFormat: +@@ -218,6 +241,15 @@ cdef extern from 'avif/avif.h': + AVIF_TRANSFER_CHARACTERISTICS_SMPTE428 + AVIF_TRANSFER_CHARACTERISTICS_HLG + ++ avifResult avifTransferCharacteristicsGetGamma( ++ avifTransferCharacteristics atc, ++ float* gamma ++ ) nogil ++ ++ avifTransferCharacteristics avifTransferCharacteristicsFindByGamma( ++ float gamma ++ ) nogil ++ + ctypedef enum avifMatrixCoefficients: + AVIF_MATRIX_COEFFICIENTS_IDENTITY + AVIF_MATRIX_COEFFICIENTS_BT709 +@@ -233,12 +265,21 @@ cdef extern from 'avif/avif.h': + AVIF_MATRIX_COEFFICIENTS_CHROMA_DERIVED_NCL + AVIF_MATRIX_COEFFICIENTS_CHROMA_DERIVED_CL + AVIF_MATRIX_COEFFICIENTS_ICTCP ++ AVIF_MATRIX_COEFFICIENTS_YCGCO_RE ++ AVIF_MATRIX_COEFFICIENTS_YCGCO_RO ++ AVIF_MATRIX_COEFFICIENTS_LAST + + ctypedef struct avifDiagnostics: + char error[256] # [AVIF_DIAGNOSTICS_ERROR_BUFFER_SIZE] + + void avifDiagnosticsClearError(avifDiagnostics* diag) nogil + ++ # Fraction utility ++ ++ ctypedef struct avifFraction: ++ int32_t n ++ int32_t d ++ + # Optional transformation structs + + ctypedef enum avifTransformFlag: +@@ -268,7 +309,7 @@ cdef extern from 'avif/avif.h': + uint8_t angle + + ctypedef struct avifImageMirror: +- uint8_t mode ++ uint8_t axis + + ctypedef struct avifCropRect: + uint32_t x +@@ -294,6 +335,10 @@ cdef extern from 'avif/avif.h': + avifDiagnostics* diag + ) nogil + ++ ctypedef struct avifContentLightLevelInformationBox: ++ uint16_t maxCLL ++ uint16_t maxPALL ++ + # avifImage + + ctypedef struct avifImage: +@@ -314,6 +359,7 @@ cdef extern from 'avif/avif.h': + avifColorPrimaries colorPrimaries + avifTransferCharacteristics transferCharacteristics + avifMatrixCoefficients matrixCoefficients ++ avifContentLightLevelInformationBox clli + avifTransformFlags transformFlags + avifPixelAspectRatioBox pasp + avifCleanApertureBox clap +@@ -347,19 +393,19 @@ cdef extern from 'avif/avif.h': + avifImage* image + ) nogil + +- void avifImageSetProfileICC( ++ avifResult avifImageSetProfileICC( + avifImage* image, + const uint8_t* icc, + size_t iccSize + ) nogil + +- void avifImageSetMetadataExif( ++ avifResult avifImageSetMetadataExif( + avifImage* image, + const uint8_t* exif, + size_t exifSize + ) nogil + +- void avifImageSetMetadataXMP( ++ avifResult avifImageSetMetadataXMP( + avifImage* image, + const uint8_t* xmp, + size_t xmpSize +@@ -422,8 +468,9 @@ cdef extern from 'avif/avif.h': + avifChromaDownsampling chromaDownsampling + avifBool avoidLibYUV + avifBool ignoreAlpha +- avifBool isFloat + avifBool alphaPremultiplied ++ avifBool isFloat ++ int maxThreads + uint8_t* pixels + uint32_t rowBytes + +@@ -438,7 +485,7 @@ cdef extern from 'avif/avif.h': + + # Convenience functions + +- void avifRGBImageAllocatePixels( ++ avifResult avifRGBImageAllocatePixels( + avifRGBImage* rgb + ) nogil + +@@ -471,62 +518,25 @@ cdef extern from 'avif/avif.h': + # YUV Utils + + int avifFullToLimitedY( +- int depth, ++ uint32_t depth, + int v + ) nogil + + int avifFullToLimitedUV( +- int depth, ++ uint32_t depth, + int v + ) nogil + + int avifLimitedToFullY( +- int depth, ++ uint32_t depth, + int v + ) nogil + + int avifLimitedToFullUV( +- int depth, ++ uint32_t depth, + int v + ) nogil + +- # removed in v0.9 +- # +- # ctypedef enum avifReformatMode: +- # AVIF_REFORMAT_MODE_YUV_COEFFICIENTS +- # AVIF_REFORMAT_MODE_IDENTITY +- +- # ctypedef struct avifReformatState: +- # float kr +- # float kg +- # float kb +- # uint32_t yuvChannelBytes +- # uint32_t rgbChannelBytes +- # uint32_t rgbChannelCount +- # uint32_t rgbPixelBytes +- # uint32_t rgbOffsetBytesR +- # uint32_t rgbOffsetBytesG +- # uint32_t rgbOffsetBytesB +- # uint32_t rgbOffsetBytesA +- # uint32_t yuvDepth +- # uint32_t rgbDepth +- # avifRange yuvRange +- # int yuvMaxChannel +- # int rgbMaxChannel +- # float yuvMaxChannelF +- # float rgbMaxChannelF +- # int uvBias +- # avifPixelFormatInfo formatInfo +- # float unormFloatTableY[1 << 12] +- # float unormFloatTableUV[1 << 12] +- # avifReformatMode mode +- +- # avifBool avifPrepareReformatState( +- # const avifImage* image, +- # const avifRGBImage* rgb, +- # avifReformatState* state +- # ) nogil +- + # Codec selection + + ctypedef enum avifCodecChoice: +@@ -536,6 +546,7 @@ cdef extern from 'avif/avif.h': + AVIF_CODEC_CHOICE_LIBGAV1 + AVIF_CODEC_CHOICE_RAV1E + AVIF_CODEC_CHOICE_SVT ++ AVIF_CODEC_CHOICE_AVM + + ctypedef enum avifCodecFlag: + AVIF_CODEC_FLAG_CAN_DECODE +@@ -657,6 +668,7 @@ cdef extern from 'avif/avif.h': + uint64_t timescale + double duration + uint64_t durationInTimescales ++ int repetitionCount + avifBool alphaPresent + avifIOStats ioStats + avifDiagnostics diag +@@ -753,12 +765,20 @@ cdef extern from 'avif/avif.h': + struct avifCodecSpecificOptions: + pass + ++ ctypedef struct avifScalingMode: ++ avifFraction horizontal ++ avifFraction vertical ++ + ctypedef struct avifEncoder: + avifCodecChoice codecChoice + int maxThreads + int speed + int keyframeInterval + uint64_t timescale ++ int repetitionCount ++ uint32_t extraLayerCount ++ int quality ++ int qualityAlpha + int minQuantizer + int maxQuantizer + int minQuantizerAlpha +@@ -766,6 +786,7 @@ cdef extern from 'avif/avif.h': + int tileRowsLog2 + int tileColsLog2 + avifBool autoTiling ++ avifScalingMode scalingMode + avifIOStats ioStats + avifDiagnostics diag + avifEncoderData* data +@@ -801,7 +822,7 @@ cdef extern from 'avif/avif.h': + avifEncoder* encoder, + uint32_t gridCols, + uint32_t gridRows, +- const avifImage* const *cellImages, ++ const avifImage* const* cellImages, + avifAddImageFlags addImageFlags + ) + +@@ -810,7 +831,7 @@ cdef extern from 'avif/avif.h': + avifRWData* output + ) nogil + +- void avifEncoderSetCodecSpecificOption( ++ avifResult avifEncoderSetCodecSpecificOption( + avifEncoder* encoder, + const char* key, + const char* value +@@ -822,6 +843,30 @@ cdef extern from 'avif/avif.h': + const avifImage* image + ) nogil + ++ avifBool avifImageIsOpaque( ++ const avifImage* image ++ ) nogil ++ ++ uint8_t* avifImagePlane( ++ const avifImage* image, ++ int channel ++ ) nogil ++ ++ uint32_t avifImagePlaneRowBytes( ++ const avifImage* image, ++ int channel ++ ) nogil ++ ++ uint32_t avifImagePlaneWidth( ++ const avifImage* image, ++ int channel ++ ) nogil ++ ++ uint32_t avifImagePlaneHeight( ++ const avifImage* image, ++ int channel ++ ) nogil ++ + avifBool avifPeekCompatibleFileType( + const avifROData* input + ) nogil diff --git a/python-imagecodecs.changes b/python-imagecodecs.changes index c9c4d9c..607e144 100644 --- a/python-imagecodecs.changes +++ b/python-imagecodecs.changes @@ -1,3 +1,13 @@ +------------------------------------------------------------------- +Wed Sep 13 11:57:38 UTC 2023 - Markéta Machová + +- Add patches for the compatibility with libavif 1.0.0: + * libavif.patch + * quantize.patch + * avif.patch + * tests.patch + * integrate.patch + ------------------------------------------------------------------- Tue Sep 12 14:21:29 UTC 2023 - Markéta Machová diff --git a/python-imagecodecs.spec b/python-imagecodecs.spec index 39b65d4..7cae79e 100644 --- a/python-imagecodecs.spec +++ b/python-imagecodecs.spec @@ -36,6 +36,16 @@ Source1: imagecodecs_distributor_setup.py Patch0: always-cythonize.patch # PATCH-FIX-UPSTREAM https://github.com/cgohlke/imagecodecs/commit/14bb6012a8c9f48df264ea996f3376e57166201a Update imagecodecs/_heif.pyx Patch1: cython3.patch +# PATCH-FIX-UPSTREAM https://github.com/cgohlke/imagecodecs/commit/d04112759c48772c4d46a2dfa4f4c6a76e23c9a9 Update imagecodecs/libavif.pxd +Patch2: libavif.patch +# PATCH-FIX-UPSTREAM https://github.com/cgohlke/imagecodecs/commit/93d1f751436e357d73eb6fdebc2af833059d9ea9 Add imagecodecs/_quantize.pyx +Patch3: quantize.patch +# PATCH-FIX-UPSTREAM https://github.com/cgohlke/imagecodecs/commit/2f548c9a4df443948f2dfcde30a7211ce8b3adc2 Update imagecodecs/_avif.pyx +Patch4: avif.patch +# PATCH-FIX-UPSTREAM https://github.com/cgohlke/imagecodecs/commit/0030b7b74fc17ceb356d1f67633ba1734108dac9 Update tests/test_imagecodecs.py +Patch5: tests.patch +# PATCH-FIX-UPSTREAM https://github.com/cgohlke/imagecodecs/commit/e9b5a984b72c9d4e14f9d37ec99389d25645c7fb Update imagecodecs/imagecodecs.py +Patch6: integrate.patch BuildRequires: %{python_module Cython >= 0.29.19} BuildRequires: %{python_module base >= 3.8} BuildRequires: %{python_module numpy-devel} @@ -60,9 +70,9 @@ BuildRequires: %{python_module Brotli} BuildRequires: %{python_module Pillow} BuildRequires: %{python_module blosc} BuildRequires: %{python_module czifile} -BuildRequires: %{python_module dask if %python-base < 3.11} -BuildRequires: %{python_module dask-array if %python-base < 3.11} -BuildRequires: %{python_module dask-delayed if %python-base < 3.11} +BuildRequires: %{python_module dask-array} +BuildRequires: %{python_module dask-delayed} +BuildRequires: %{python_module dask} BuildRequires: %{python_module imagecodecs >= %{version}} BuildRequires: %{python_module lz4} BuildRequires: %{python_module matplotlib >= 3.3} @@ -102,6 +112,7 @@ BuildRequires: pkgconfig(blosc2) >= 2.7.1 BuildRequires: pkgconfig(bzip2) BuildRequires: pkgconfig(cfitsio) BuildRequires: pkgconfig(lcms2) +BuildRequires: pkgconfig(libavif) >= 1.0.0 BuildRequires: pkgconfig(libbrotlicommon) BuildRequires: pkgconfig(libheif) # Beta, not available in minimum version @@ -122,8 +133,6 @@ BuildRequires: pkgconfig(zlib-ng) BuildRequires: zfp-devel BuildRequires: pkgconfig(SvtAv1Dec) BuildRequires: pkgconfig(SvtAv1Enc) -# 32-bit tests fail -BuildRequires: pkgconfig(libavif) %endif %endif %python_subpackages @@ -144,6 +153,9 @@ Bitshuffle, and Float24 (24-bit floating point). %setup -q -n imagecodecs-%{version} # the patch from github requires unix line endings to apply dos2unix tests/test_imagecodecs.py +dos2unix imagecodecs/libavif.pxd +dos2unix imagecodecs/_avif.pyx +dos2unix imagecodecs/imagecodecs.py %autopatch -p1 cp %SOURCE1 ./ @@ -179,8 +191,6 @@ donttest="$donttest or (test_tiff and (webp or lerc or jpeg))" %ifarch %ix86 %arm32 donttest="$donttest or spng" %endif -# no dask because of numba for python 3.11 -python311_donttest="or imagecodecs.imagecodecs" %pytest_arch -n auto tests -rsXfE --doctest-modules %{$python_sitearch}/imagecodecs/imagecodecs.py -k "not ($donttest ${$python_donttest})" %endif diff --git a/quantize.patch b/quantize.patch new file mode 100644 index 0000000..56fc899 --- /dev/null +++ b/quantize.patch @@ -0,0 +1,233 @@ +From 93d1f751436e357d73eb6fdebc2af833059d9ea9 Mon Sep 17 00:00:00 2001 +From: Christoph Gohlke +Date: Sat, 2 Sep 2023 22:41:40 -0700 +Subject: [PATCH] Add imagecodecs/_quantize.pyx + +--- + imagecodecs/_quantize.pyx | 217 ++++++++++++++++++++++++++++++++++++++ + 1 file changed, 217 insertions(+) + create mode 100644 imagecodecs/_quantize.pyx + +diff --git a/imagecodecs/_quantize.pyx b/imagecodecs/_quantize.pyx +new file mode 100644 +index 0000000..912eb32 +--- /dev/null ++++ b/imagecodecs/_quantize.pyx +@@ -0,0 +1,217 @@ ++# imagecodecs/_quantize.pyx ++# distutils: language = c ++# cython: language_level = 3 ++# cython: boundscheck=False ++# cython: wraparound=False ++# cython: cdivision=True ++# cython: nonecheck=False ++ ++# Copyright (c) 2023, Christoph Gohlke ++# All rights reserved. ++# ++# Redistribution and use in source and binary forms, with or without ++# modification, are permitted provided that the following conditions are met: ++# ++# 1. Redistributions of source code must retain the above copyright notice, ++# this list of conditions and the following disclaimer. ++# ++# 2. Redistributions in binary form must reproduce the above copyright notice, ++# this list of conditions and the following disclaimer in the documentation ++# and/or other materials provided with the distribution. ++# ++# 3. Neither the name of the copyright holder nor the names of its ++# contributors may be used to endorse or promote products derived from ++# this software without specific prior written permission. ++# ++# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ++# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE ++# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR ++# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF ++# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS ++# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN ++# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ++# POSSIBILITY OF SUCH DAMAGE. ++ ++"""Quantize codec for the imagecodecs package.""" ++ ++__version__ = '2023.9.4' ++ ++include '_shared.pxi' ++ ++from nc4var cimport * ++ ++from libc.math cimport log10, log2, floor, ceil, round, pow ++ ++ ++class QUANTIZE: ++ """Quantize codec constants.""" ++ ++ available = True ++ ++ class MODE(enum.IntEnum): ++ """Quantize mode.""" ++ ++ NOQUANTIZE = NC_NOQUANTIZE ++ BITGROOM = NC_QUANTIZE_BITGROOM ++ GRANULARBR = NC_QUANTIZE_GRANULARBR ++ BITROUND = NC_QUANTIZE_BITROUND ++ SCALE = 100 ++ ++ ++class QuantizeError(RuntimeError): ++ """Quantize codec exceptions.""" ++ ++ ++def quantize_version(): ++ """Return nc4var library version string.""" ++ return f'nc4var {NC_VERSION_MAJOR}.{NC_VERSION_MINOR}.{NC_VERSION_PATCH}' ++ ++ ++def quantize_encode(data, mode, int nsd, out=None): ++ """Return quantized floating point data. ++ ++ ``nsd`` ("number of significant digits") is interpreted differently ++ by the modes: ++ ++ - BitGroom and Scale: number of significant decimal digits ++ - BitRound: number of significant binary digits (bits). ++ - Granular BitRound: ? ++ ++ """ ++ cdef: ++ numpy.ndarray src ++ numpy.ndarray dst ++ size_t src_size ++ nc_type src_type ++ int ret ++ int range_error = 0 ++ int strict_nc3 = 0 ++ int quantize_mode = NC_NOQUANTIZE ++ # uint64_t fill_value = 0 ++ ++ data = numpy.ascontiguousarray(data) ++ src = data ++ src_size = src.size ++ ++ if src.dtype.kind != b'f' and src.itemsize not in {4, 8}: ++ raise ValueError('not a floating-point array') ++ ++ if src.itemsize == 4: ++ src_type = NC_FLOAT ++ else: ++ src_type = NC_DOUBLE ++ ++ if mode in { ++ NC_NOQUANTIZE, ++ NC_QUANTIZE_BITGROOM, ++ NC_QUANTIZE_GRANULARBR, ++ NC_QUANTIZE_BITROUND, ++ 100 ++ }: ++ quantize_mode = mode ++ elif mode == 'bitround': ++ quantize_mode = NC_QUANTIZE_BITROUND ++ elif mode == 'bitgroom': ++ quantize_mode = NC_QUANTIZE_BITGROOM ++ elif mode in {'granularbr', 'gbr'}: ++ quantize_mode = NC_QUANTIZE_GRANULARBR ++ elif mode == 'scale': ++ quantize_mode = 100 ++ elif mode == 'noquantize': ++ quantize_mode = NC_NOQUANTIZE ++ else: ++ raise ValueError(f'invalid quantize mode {mode!r}') ++ ++ if not 0 <= nsd < 64: ++ raise ValueError(f'invalid number of significant digits {nsd!r}') ++ ++ out = _create_array(out, data.shape, data.dtype) ++ dst = out ++ ++ with nogil: ++ if quantize_mode == 100: ++ if src_type == NC_FLOAT: ++ quantize_scale_f( ++ src.data, ++ dst.data, ++ src_size, ++ nsd ++ ) ++ else: ++ quantize_scale_d( ++ src.data, ++ dst.data, ++ src_size, ++ nsd ++ ) ++ else: ++ ret = nc4_convert_type( ++ src.data, ++ dst.data, ++ src_type, ++ src_type, ++ src_size, ++ &range_error, ++ NULL, # fill_value ++ 1, # strict_nc3 ++ quantize_mode, ++ nsd ++ ) ++ if ret < 0: ++ raise QuantizeError(f'nc4_convert_type returnd {ret!r}') ++ ++ return out ++ ++ ++def quantize_decode(data, mode, nsd, out=None): ++ """Return de-quantized data. Raise QuantizeError if lossy.""" ++ if mode != NC_NOQUANTIZE: ++ raise QuantizeError(f'Quantize mode {mode} is lossy.') ++ return data ++ ++############################################################################### ++ ++# quantize_scale ++# Data is quantized using round(scale*data)/scale, where scale is 2**bits, ++# and bits is determined from the nsd. For example, if nsd=1, bits will be 4. ++# https://github.com/Blosc/bcolz utils.py ++ ++cdef void quantize_scale_f( ++ const float* data, ++ float* out, ++ ssize_t size, ++ int nsb ++) nogil: ++ cdef: ++ float scale ++ double exp ++ ssize_t i ++ ++ exp = log10(pow(10.0, -nsb)) ++ exp = floor(exp) if exp < 0.0 else ceil(exp) ++ scale = pow(2.0, ceil(log2(pow(10.0, -exp)))) ++ ++ for i in range(size): ++ out[i] = round(data[i] * scale) / scale ++ ++ ++cdef void quantize_scale_d( ++ const double* data, ++ double* out, ++ ssize_t size, ++ int nsb ++) nogil: ++ cdef: ++ double scale ++ double exp ++ ssize_t i ++ ++ exp = log10(pow(10.0, -nsb)) ++ exp = floor(exp) if exp < 0.0 else ceil(exp) ++ scale = pow(2.0, ceil(log2(pow(10.0, -exp)))) ++ ++ for i in range(size): ++ out[i] = round(data[i] * scale) / scale diff --git a/tests.patch b/tests.patch new file mode 100644 index 0000000..e9bb107 --- /dev/null +++ b/tests.patch @@ -0,0 +1,199 @@ +From 0030b7b74fc17ceb356d1f67633ba1734108dac9 Mon Sep 17 00:00:00 2001 +From: Christoph Gohlke +Date: Sun, 3 Sep 2023 09:36:27 -0700 +Subject: [PATCH] Update tests/test_imagecodecs.py + +--- + tests/test_imagecodecs.py | 131 ++++++++++++++++++++++++++++++-------- + 1 file changed, 106 insertions(+), 25 deletions(-) + +Index: imagecodecs-2023.3.16/tests/test_imagecodecs.py +=================================================================== +--- imagecodecs-2023.3.16.orig/tests/test_imagecodecs.py ++++ imagecodecs-2023.3.16/tests/test_imagecodecs.py +@@ -79,6 +79,7 @@ except ImportError as exc: + + try: + import zarr ++ + from imagecodecs import numcodecs + except ImportError: + SKIP_NUMCODECS = True +@@ -172,7 +173,7 @@ def test_dependency_exist(name): + if SKIP_NUMCODECS and IS_PYPY: + mayfail = True + elif name in ('blosc', 'blosc2', 'snappy'): +- if IS_PYPY or sys.version_info[1] >= 10: ++ if IS_PYPY or not IS_CG: + mayfail = True + try: + importlib.import_module(name) +@@ -194,7 +195,7 @@ def test_version_functions(): + def test_stubs(): + """Test stub attributes for non-existing extension.""" + with pytest.raises(AttributeError): +- imagecodecs._STUB ++ assert imagecodecs._STUB + _add_codec('_stub') + assert not imagecodecs._STUB # typing: ignore + assert not imagecodecs._STUB.available +@@ -873,6 +874,74 @@ def test_lzw_msb(): + + + @pytest.mark.skipif( ++ not imagecodecs.QUANTIZE.available, reason='QUANTIZE missing' ++) ++@pytest.mark.parametrize( ++ 'mode', ['bitgroom', 'granularbr', 'bitround', 'scale'] ++) ++@pytest.mark.parametrize('dtype', ['f4', 'f8']) ++def test_quantize_roundtrip(mode, dtype): ++ """Test quantize roundtrips.""" ++ nsd = 12 ++ atol = 0.006 ++ if mode == 'bitgroom': ++ nsd = (nsd - 1) // 3 # bits = math.ceil(nsd * 3.32) + 1 ++ if dtype == 'f4': ++ nsd //= 2 ++ atol = 0.5 ++ data = numpy.linspace(-2.1, 31.4, 51, dtype=dtype).reshape((3, 17)) ++ encoded = imagecodecs.quantize_encode(data, mode, nsd) ++ out = data.copy() ++ imagecodecs.quantize_encode(data, mode, nsd, out=out) ++ assert_array_equal(out, encoded) ++ assert_allclose(data, encoded, atol=atol) ++ ++ ++@pytest.mark.skipif( ++ SKIP_NUMCODECS or not imagecodecs.QUANTIZE.available, ++ reason='QUANTIZE missing', ++) ++@pytest.mark.parametrize('nsd', [1, 4]) ++@pytest.mark.parametrize('dtype', ['f4', 'f8']) ++def test_quantize_bitround(dtype, nsd): ++ """Test BitRound quantize against numcodecs.""" ++ from numcodecs import BitRound ++ ++ from imagecodecs.numcodecs import Quantize ++ ++ # TODO: 31.4 fails ++ data = numpy.linspace(-2.1, 31.5, 51, dtype=dtype).reshape((3, 17)) ++ encoded = Quantize( ++ mode=imagecodecs.QUANTIZE.MODE.BITROUND, ++ nsd=nsd, ++ ).encode(data) ++ nc = BitRound(keepbits=nsd) ++ encoded2 = nc.decode(nc.encode(data)) ++ assert_array_equal(encoded, encoded2) ++ ++ ++@pytest.mark.skipif( ++ SKIP_NUMCODECS or not imagecodecs.QUANTIZE.available, ++ reason='QUANTIZE missing', ++) ++@pytest.mark.parametrize('nsd', [1, 4]) ++@pytest.mark.parametrize('dtype', ['f4', 'f8']) ++def test_quantize_scale(dtype, nsd): ++ """Test Scale quantize against numcodecs.""" ++ from numcodecs import Quantize as Quantize2 ++ ++ from imagecodecs.numcodecs import Quantize ++ ++ data = numpy.linspace(-2.1, 31.4, 51, dtype=dtype).reshape((3, 17)) ++ encoded = Quantize( ++ mode=imagecodecs.QUANTIZE.MODE.SCALE, ++ nsd=nsd, ++ ).encode(data) ++ encoded2 = Quantize2(digits=nsd, dtype=dtype).encode(data) ++ assert_array_equal(encoded, encoded2) ++ ++ ++@pytest.mark.skipif( + not (imagecodecs.LZW.available and imagecodecs.DELTA.available), + reason='skip', + ) +@@ -1818,7 +1896,7 @@ def test_rgbe_roundtrip(): + @pytest.mark.skipif(not imagecodecs.CMS.available, reason='cms missing') + def test_cms_profile(): + """Test cms_profile function.""" +- from imagecodecs import cms_profile, cms_profile_validate, CmsError ++ from imagecodecs import CmsError, cms_profile, cms_profile_validate + + with pytest.raises(CmsError): + cms_profile_validate(b'12345') +@@ -1934,7 +2012,7 @@ def test_cms_profile(): + @pytest.mark.skipif(not imagecodecs.CMS.available, reason='cms missing') + def test_cms_output_shape(): + """Test _cms_output_shape function.""" +- from imagecodecs._cms import _cms_output_shape, _cms_format ++ from imagecodecs._cms import _cms_format, _cms_output_shape + + for args, colorspace, planar, expected in ( + (((6, 7), 'u1', 'gray'), 'gray', 0, (6, 7)), +@@ -2086,7 +2164,7 @@ def test_cms_format(): + @pytest.mark.parametrize('out', [None, True]) + def test_cms_identity_transforms(dtype, outdtype, planar, outplanar, out): + """Test CMS identity transforms.""" +- from imagecodecs import cms_transform, cms_profile ++ from imagecodecs import cms_profile, cms_transform + + shape = (3, 256, 253) if planar else (256, 253, 3) + dtype = numpy.dtype(dtype) +@@ -2480,7 +2558,10 @@ def test_avif_strict_disabled(): + + + @pytest.mark.skipif(not IS_CG, reason='avif missing') +-@pytest.mark.parametrize('codec', ['auto', 'aom', 'rav1e', 'svt']) # 'libgav1' ++@pytest.mark.parametrize( ++ 'codec', ++ ['auto', 'aom', 'rav1e', 'svt'], # 'libgav1', 'avm' ++) + def test_avif_encoder(codec): + """Test various AVIF encoder codecs.""" + data = numpy.load(datafiles('rgb.u1.npy')) +@@ -2490,9 +2571,9 @@ def test_avif_encoder(codec): + else: + pixelformat = None + encoded = imagecodecs.avif_encode( +- data, level=6, codec=codec, pixelformat=pixelformat ++ data, level=95, codec=codec, pixelformat=pixelformat, numthreads=2 + ) +- decoded = imagecodecs.avif_decode(encoded) ++ decoded = imagecodecs.avif_decode(encoded, numthreads=2) + assert_allclose(decoded, data, atol=5, rtol=0) + + +@@ -3353,6 +3434,8 @@ def test_image_roundtrips(codec, dtype, + data, bitspersample=12, *args, **kwargs + ) + ++ if level: ++ level += 95 + atol = 10 + elif codec == 'heif': + if not imagecodecs.HEIF.available: +@@ -3431,13 +3514,8 @@ def test_image_roundtrips(codec, dtype, + if level < 100: + atol *= 4 + assert_allclose(data, decoded, atol=atol) +- elif codec == 'avif' and level == 5: +- if dtype.itemsize > 1: +- # TODO: bug in libavif? +- pytest.xfail('why does this fail?') +- atol = 32 +- else: +- atol = 6 ++ elif codec == 'avif' and level == 94: ++ atol = 38 if dtype.itemsize > 1 else 6 + assert_allclose(data, decoded, atol=atol) + else: + assert_array_equal(data, decoded, verbose=True) +@@ -3847,7 +3926,7 @@ def test_numcodecs(codec, photometric): + if photometric != 'rgb': + pytest.xfail('AVIF does not support grayscale') + compressor = numcodecs.Avif( +- level=0, ++ level=100, + speed=None, + tilelog2=None, + bitspersample=None,