Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use libjpeg-turbo for all Lossless JPEG bit depths #105

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ Build requirements:
- `libheif <https://github.com/strukturag/libheif>`_ 1.17.6
(`libde265 <https://github.com/strukturag/libde265>`_ 1.0.15,
`x265 <https://bitbucket.org/multicoreware/x265_git/src/master/>`_ 3.6)
- `libjpeg-turbo <https://github.com/libjpeg-turbo/libjpeg-turbo>`_ 3.0.3
- `libjpeg-turbo <https://github.com/libjpeg-turbo/libjpeg-turbo>`_ 6ec8e41f50e5a83fe078732cbf0360272165ed45
- `libjxl <https://github.com/libjxl/libjxl>`_ 0.10.2
- `liblzma <https://github.com/tukaani-project/xz>`_ 5.6.2
- `libpng <https://github.com/glennrp/libpng>`_ 1.6.43
Expand Down
22 changes: 13 additions & 9 deletions imagecodecs/_jpeg8.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ def jpeg8_encode(
(src.dtype == numpy.uint8 or src.dtype == numpy.uint16)
and src.ndim in {2, 3}
# src.nbytes <= 2147483647 and # limit to 2 GB
and samples in {1, 3, 4}
and samples in {1, 2, 3, 4}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems to work as expected with 2 components:

import sys

from imagecodecs import imread, jpeg8_decode, jpeg8_encode
from numpy.testing import assert_array_equal

filename = sys.argv[1]

image = imread(filename)
enc = jpeg8_encode(
    image,
    lossless=True,
    predictor=1,
    bitspersample=16,
)
dec = jpeg8_decode(enc)
assert_array_equal(image, dec)

Using this input file: 16

and src.strides[src.ndim-1] == src.itemsize
and (src.ndim == 2 or src.strides[1] == samples * src.itemsize)
):
Expand All @@ -152,10 +152,14 @@ def jpeg8_encode(

if bitspersample is not None:
if (
bitspersample not in {8, 12, 16}
or src.itemsize == 1 and bitspersample > 8
not (lossless and 2 <= bitspersample <= 16)
or (not lossless and bitspersample not in {8, 12})
or src.itemsize != (bitspersample + 7) // 8
):
raise ValueError(f'invalid {bitspersample=}')
raise ValueError(
f'invalid {bitspersample=}'
f' (in combination with {src.itemsize=})'
)
data_precision = bitspersample

# if validate and bitspersample == 12 and not _check_range(src):
Expand Down Expand Up @@ -264,19 +268,19 @@ def jpeg8_encode(

jpeg_start_compress(&cinfo, 1)

if cinfo.data_precision == 8:
if cinfo.data_precision <= 8:
while cinfo.next_scanline < cinfo.image_height:
rowpointer8 = <JSAMPROW> (
<char*> src.data + cinfo.next_scanline * rowstride
)
jpeg_write_scanlines(&cinfo, &rowpointer8, 1)
elif cinfo.data_precision == 12:
elif cinfo.data_precision <= 12:
while cinfo.next_scanline < cinfo.image_height:
rowpointer12 = <J12SAMPROW> (
<char*> src.data + cinfo.next_scanline * rowstride
)
jpeg12_write_scanlines(&cinfo, &rowpointer12, 1)
elif cinfo.data_precision == 16:
elif cinfo.data_precision <= 16:
while cinfo.next_scanline < cinfo.image_height:
rowpointer16 = <J16SAMPROW> (
<char*> src.data + cinfo.next_scanline * rowstride
Expand Down Expand Up @@ -410,13 +414,13 @@ def jpeg8_decode(
while cinfo.output_scanline < cinfo.output_height:
jpeg_read_scanlines(&cinfo, &rowpointer8, 1)
rowpointer8 += rowstride
elif cinfo.data_precision == 12:
elif cinfo.data_precision <= 12:
rowpointer12 = <J12SAMPROW> dst.data
while cinfo.output_scanline < cinfo.output_height:
jpeg12_read_scanlines(&cinfo, &rowpointer12, 1)
rowpointer12 += rowstride
else:
# elif cinfo.data_precision == 16:
# elif cinfo.data_precision <= 16:
rowpointer16 = <J16SAMPROW> dst.data
while cinfo.output_scanline < cinfo.output_height:
jpeg16_read_scanlines(&cinfo, &rowpointer16, 1)
Expand Down
9 changes: 2 additions & 7 deletions imagecodecs/imagecodecs.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@
- `libheif <https://github.com/strukturag/libheif>`_ 1.17.6
(`libde265 <https://github.com/strukturag/libde265>`_ 1.0.15,
`x265 <https://bitbucket.org/multicoreware/x265_git/src/master/>`_ 3.6)
- `libjpeg-turbo <https://github.com/libjpeg-turbo/libjpeg-turbo>`_ 3.0.3
- `libjpeg-turbo <https://github.com/libjpeg-turbo/libjpeg-turbo>`_ 6ec8e41f50e5a83fe078732cbf0360272165ed45
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the latest sha1 from the dev branch. No official release tag.

- `libjxl <https://github.com/libjxl/libjxl>`_ 0.10.2
- `liblzma <https://github.com/tukaani-project/xz>`_ 5.6.2
- `libpng <https://github.com/glennrp/libpng>`_ 1.6.43
Expand Down Expand Up @@ -1715,8 +1715,7 @@ def jpeg_decode(
msg = str(exc)

if (
'Unsupported JPEG data precision' in msg
or 'Unsupported color conversion' in msg
'Unsupported color conversion' in msg
or 'Bogus Huffman table definition' in msg
or 'SOF type' in msg
):
Expand Down Expand Up @@ -1746,10 +1745,6 @@ def jpeg_encode(
out: int | bytearray | None = None,
) -> bytes | bytearray:
"""Return JPEG encoded image."""
if lossless and bitspersample not in {None, 8, 12, 16}:
return imagecodecs.ljpeg_encode(
data, bitspersample=bitspersample, out=out
)
return imagecodecs.jpeg8_encode(
data,
level=level,
Expand Down
Binary file removed tests/ljpeg/dng0.ljp
Binary file not shown.
Binary file modified tests/ljpeg/dng1.ljp
Binary file not shown.
Binary file modified tests/ljpeg/dng2.ljp
Binary file not shown.
Binary file modified tests/ljpeg/dng3.ljp
Binary file not shown.
Binary file modified tests/ljpeg/dng4.ljp
Binary file not shown.
Binary file modified tests/ljpeg/dng5.ljp
Binary file not shown.
Binary file modified tests/ljpeg/dng6.ljp
Binary file not shown.
Binary file modified tests/ljpeg/dng7.ljp
Binary file not shown.
21 changes: 7 additions & 14 deletions tests/test_imagecodecs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2922,6 +2922,7 @@ def test_mozjpeg():
)
def test_ljpeg(fname, result, codec):
"""Test Lossless JPEG decoders."""
kwargs = {}
if codec == 'jpeg8':
if not imagecodecs.JPEG8.available:
pytest.skip('jpeg8 missing')
Expand All @@ -2930,20 +2931,14 @@ def test_ljpeg(fname, result, codec):
decode = imagecodecs.jpeg8_decode
check = imagecodecs.jpeg8_check
if fname in {
'2ch.ljp', # Unsupported JPEG data precision 14
'2dht.ljp', # Unsupported color conversion request
'rgb24.ljp', # Unsupported color conversion request
'linearraw.ljp', # Unsupported color conversion request
'dng0.ljp', # Invalid progressive/lossless parameters Ss=0 ...
# 'dng1.ljp', # Bogus Huffman table definition
# 'dng2.ljp', # Bogus Huffman table definition
# 'dng3.ljp', # Bogus Huffman table definition
# 'dng4.ljp', # Bogus Huffman table definition
# 'dng5.ljp', # Bogus Huffman table definition
# 'dng6.ljp', # Bogus Huffman table definition
# 'dng7.ljp', # Bogus Huffman table definition
}:
pytest.xfail('libjpeg-turbo does not support this case')
elif fname in {
'2dht.ljp',
'linearraw.ljp',
}:
kwargs["colorspace"] = "YCBCR"
elif codec == 'ljpeg':
if not imagecodecs.LJPEG.available:
pytest.skip('ljpeg missing')
Expand All @@ -2954,8 +2949,6 @@ def test_ljpeg(fname, result, codec):
pytest.skip('jpegsof3 missing')
if fname in {'dcm6.ljp', 'dcm7.ljp'}:
return # jpegsof3 segfault
if fname == 'dng0.ljp':
pytest.xfail('jpegsof3 known failure or crash')
decode = imagecodecs.jpegsof3_decode
check = imagecodecs.jpegsof3_check
if fname == 'pvrg.ljp':
Expand All @@ -2967,7 +2960,7 @@ def test_ljpeg(fname, result, codec):
pytest.skip(f'{fname} not found')

assert check(data) in {None, True}
decoded = decode(data)
decoded = decode(data, **kwargs)

shape, dtype, index, value = result
assert decoded.shape == shape
Expand Down