Merge pull request #2156 from libvips/add-openjpeg

Add JPEG2000 support
This commit is contained in:
John Cupitt 2021-03-27 17:10:42 +00:00 committed by GitHub
commit cb660b32fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 2665 additions and 80 deletions

View File

@ -56,6 +56,7 @@ jobs:
liblcms2-dev libpoppler-glib-dev librsvg2-dev
libgif-dev libopenexr-dev libpango1.0-dev
libgsf-1-dev libopenslide-dev libffi-dev
libopenjp2-7-dev
- name: Install macOS dependencies
if: contains(matrix.os, 'macos')
@ -69,6 +70,7 @@ jobs:
librsvg libspng libtiff
little-cms2 openexr openslide
orc pango poppler webp
openjpeg
- name: Install Clang 10
env:

View File

@ -20,6 +20,8 @@
- add vips_fitsload_source(), vips_niftiload_source()
- png and gif load note background colour as metadata [781545872]
- add vips_image_[set|get]_array_double()
- add GIF load with libnsgif
- add JPEG2000 load and save
22/12/20 start 8.10.6
- don't seek on bad file descriptors [kleisauke]

View File

@ -770,6 +770,36 @@ VIPS_CFLAGS="$VIPS_CFLAGS $NIFTI_CFLAGS"
VIPS_INCLUDES="$VIPS_INCLUDES $NIFTI_INCLUDES"
VIPS_LIBS="$VIPS_LIBS $NIFTI_LIBS"
# openjpeg
AC_ARG_WITH([libopenjp2],
AS_HELP_STRING([--without-libopenjp2],
[build without libopenjp2 (default: test)]))
if test x"$with_libopenjp2" != x"no"; then
PKG_CHECK_MODULES(LIBOPENJP2, libopenjp2 >= 2.3,
[AC_DEFINE(HAVE_LIBOPENJP2,1,
[define if you have libopenjp2 >=2.2 installed.])
with_libopenjp2=yes
PACKAGES_USED="$PACKAGES_USED libopenjp2"
],
[AC_MSG_WARN([libopenjp2 not found; disabling libopenjp2 support])
with_libopenjp2=no
]
)
# 2.3 and earlier have threading problems
PKG_CHECK_MODULES(LIBOPENJP2_THREADING, libopenjp2 >= 2.4,
[AC_DEFINE(HAVE_LIBOPENJP2_THREADING,1,[define if your libopenjp2 threading works.])
],
[:
]
)
fi
VIPS_CFLAGS="$VIPS_CFLAGS $LIBOPENJP2_CFLAGS"
VIPS_INCLUDES="$VIPS_INCLUDES $LIBOPENJP2_INCLUDES"
VIPS_LIBS="$VIPS_LIBS $LIBOPENJP2_LIBS"
# libheif
AC_ARG_WITH([heif],
AS_HELP_STRING([--without-heif], [build without libheif (default: test)]))
@ -1420,6 +1450,8 @@ PDF load with poppler-glib: $with_poppler
SVG load with librsvg-2.0: $with_rsvg
(requires librsvg-2.0 2.34.0 or later)
EXR load with OpenEXR: $with_OpenEXR
JPEG2000 load/save with libopenjp2: $with_libopenjp2
(requires libopenjp2 2.2 or later)
slide load with OpenSlide: $with_openslide
(requires openslide-3.3.0 or later)
Matlab load with matio: $with_matio

View File

@ -263,12 +263,6 @@ TAB_SIZE = 8
ALIASES =
# This tag can be used to specify a number of word-keyword mappings (TCL only).
# A mapping has the form "name=value". For example adding "class=itcl::class"
# will allow you to use the command class in the itcl::class meaning.
TCL_SUBST =
# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
# only. Doxygen will then generate output that is more tailored for C. For
# instance, some of the names that are used will be different. The list of all

View File

@ -5,70 +5,72 @@ endif
noinst_LTLIBRARIES = libforeign.la
libforeign_la_SOURCES = \
pforeign.h \
heifload.c \
heifsave.c \
niftiload.c \
niftisave.c \
quantise.c \
exif.c \
nsgifload.c \
analyze2vips.c \
analyzeload.c \
cairo.c \
pdfload.c \
pdfiumload.c \
svgload.c \
radiance.c \
radload.c \
radsave.c \
ppmload.c \
ppmsave.c \
csvload.c \
csvsave.c \
matrixload.c \
matrixsave.c \
dzsave.c \
rawload.c \
rawsave.c \
vipsload.c \
vipssave.c \
dbh.h \
analyzeload.c \
analyze2vips.c \
foreign.c \
matlab.c \
matload.c \
magick.h \
magick.c \
magick2vips.c \
magickload.c \
magick7load.c \
magicksave.c \
spngload.c \
pngload.c \
pngsave.c \
vipspng.c \
openexr2vips.c \
openexrload.c \
dzsave.c \
exif.c \
fits.c \
fitsload.c \
fitssave.c \
tiff.h \
tiff.c \
vips2tiff.c \
tiff2vips.c \
tiffload.c \
tiffsave.c \
openslide2vips.c \
openslideload.c \
webpload.c \
webpsave.c \
webp2vips.c \
vips2webp.c \
vips2jpeg.c \
foreign.c \
heifload.c \
heifsave.c \
jp2kload.c \
jp2ksave.c \
jpeg2vips.c \
jpeg.h \
jpegload.c \
jpegsave.c
jpegsave.c \
magick2vips.c \
magick7load.c \
magick.c \
magick.h \
magickload.c \
magicksave.c \
matlab.c \
matload.c \
matrixload.c \
matrixsave.c \
niftiload.c \
niftisave.c \
nsgifload.c \
openexr2vips.c \
openexrload.c \
openslide2vips.c \
openslideload.c \
pdfiumload.c \
pdfload.c \
pforeign.h \
pngload.c \
pngsave.c \
ppmload.c \
ppmsave.c \
quantise.c \
radiance.c \
radload.c \
radsave.c \
rawload.c \
rawsave.c \
spngload.c \
svgload.c \
tiff2vips.c \
tiff.c \
tiff.h \
tiffload.c \
tiffsave.c \
vips2jpeg.c \
vips2tiff.c \
vips2webp.c \
vipsload.c \
vipspng.c \
vipssave.c \
webp2vips.c \
webpload.c \
webpsave.c
EXTRA_DIST = libnsgif

View File

@ -2176,6 +2176,13 @@ vips_foreign_operation_init( void )
extern GType vips_foreign_load_svg_buffer_get_type( void );
extern GType vips_foreign_load_svg_source_get_type( void );
extern GType vips_foreign_load_jp2k_file_get_type( void );
extern GType vips_foreign_load_jp2k_buffer_get_type( void );
extern GType vips_foreign_load_jp2k_source_get_type( void );
extern GType vips_foreign_save_jp2k_file_get_type( void );
extern GType vips_foreign_save_jp2k_buffer_get_type( void );
extern GType vips_foreign_save_jp2k_target_get_type( void );
extern GType vips_foreign_load_heif_file_get_type( void );
extern GType vips_foreign_load_heif_buffer_get_type( void );
extern GType vips_foreign_load_heif_source_get_type( void );
@ -2249,8 +2256,17 @@ vips_foreign_operation_init( void )
vips_foreign_load_svg_source_get_type();
#endif /*HAVE_RSVG*/
#ifdef HAVE_LIBOPENJP2
vips_foreign_load_jp2k_file_get_type();
vips_foreign_load_jp2k_buffer_get_type();
vips_foreign_load_jp2k_source_get_type();
vips_foreign_save_jp2k_file_get_type();
vips_foreign_save_jp2k_buffer_get_type();
vips_foreign_save_jp2k_target_get_type();
#endif /*HAVE_LIBOPENJP2*/
#ifdef HAVE_NSGIF
vips_foreign_load_nsgif_file_get_type();
vips_foreign_load_nsgif_file_get_type();
vips_foreign_load_nsgif_buffer_get_type();
vips_foreign_load_nsgif_source_get_type();
#endif /*HAVE_NSGIF*/

View File

@ -230,7 +230,8 @@ vips_foreign_load_heif_build( VipsObject *object )
printf( "vips_foreign_load_heif_build:\n" );
#endif /*DEBUG*/
if( vips_source_rewind( heif->source ) )
if( heif->source &&
vips_source_rewind( heif->source ) )
return( -1 );
if( !heif->ctx ) {
@ -245,7 +246,6 @@ vips_foreign_load_heif_build( VipsObject *object )
}
}
if( VIPS_OBJECT_CLASS( vips_foreign_load_heif_parent_class )->
build( object ) )
return( -1 );
@ -253,7 +253,6 @@ vips_foreign_load_heif_build( VipsObject *object )
return( 0 );
}
static const char *heif_magic[] = {
"ftypheic", /* A regular heif image */
"ftypheix", /* Extended range (>8 bit) image */

View File

@ -380,7 +380,7 @@ vips_foreign_save_heif_build( VipsObject *object )
chroma = heif->subsample_mode == VIPS_FOREIGN_SUBSAMPLE_OFF ||
( heif->subsample_mode == VIPS_FOREIGN_SUBSAMPLE_AUTO &&
heif->Q > 90 ) ? "444" : "420";
heif->Q >= 90 ) ? "444" : "420";
error = heif_encoder_set_parameter_string( heif->encoder,
"chroma", chroma );
if( error.code &&
@ -738,7 +738,7 @@ vips_foreign_save_heif_target_init( VipsForeignSaveHeifTarget *target )
* Use @speed to control the CPU effort spent improving compression.
* This is currently only applicable to AV1 encoders, defaults to 5.
*
* Chroma subsampling is normally automatically disabled for Q > 90. You can
* Chroma subsampling is normally automatically disabled for Q >= 90. You can
* force the subsampling mode with @subsample_mode.
*
* See also: vips_image_write_to_file(), vips_heifload().

1233
libvips/foreign/jp2kload.c Normal file

File diff suppressed because it is too large Load Diff

1181
libvips/foreign/jp2ksave.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -557,7 +557,7 @@ vips_foreign_save_jpeg_mime_init( VipsForeignSaveJpegMime *mime )
* If @strip is set, no EXIF data, IPTC data, ICC profile or XMP metadata is
* written into the output file.
*
* Chroma subsampling is normally automatically disabled for Q > 90. You can
* Chroma subsampling is normally automatically disabled for Q >= 90. You can
* force the subsampling mode with @subsample_mode.
*
* If @trellis_quant is set and the version of libjpeg supports it

View File

@ -245,6 +245,8 @@ void vips__heif_error( struct heif_error *error );
struct heif_image;
void vips__heif_image_print( struct heif_image *img );
extern const char *vips__jp2k_suffs[];
#ifdef __cplusplus
}
#endif /*__cplusplus*/

View File

@ -372,7 +372,7 @@ int vips_openslideload_source( VipsSource *source, VipsImage **out, ... )
/**
* VipsForeignSubsample:
* @VIPS_FOREIGN_SUBSAMPLE_AUTO: prevent subsampling when quality > 90
* @VIPS_FOREIGN_SUBSAMPLE_AUTO: prevent subsampling when quality >= 90
* @VIPS_FOREIGN_SUBSAMPLE_ON: always perform subsampling
* @VIPS_FOREIGN_SUBSAMPLE_OFF: never perform subsampling
*
@ -676,6 +676,19 @@ int vips_niftiload_source( VipsSource *source, VipsImage **out, ... )
int vips_niftisave( VipsImage *in, const char *filename, ... )
__attribute__((sentinel));
int vips_jp2kload( const char *filename, VipsImage **out, ... )
__attribute__((sentinel));
int vips_jp2kload_buffer( void *buf, size_t len, VipsImage **out, ... )
__attribute__((sentinel));
int vips_jp2kload_source( VipsSource *source, VipsImage **out, ... )
__attribute__((sentinel));
int vips_jp2ksave( VipsImage *in, const char *filename, ... )
__attribute__((sentinel));
int vips_jp2ksave_buffer( VipsImage *in, void **buf, size_t *len, ... )
__attribute__((sentinel));
int vips_jp2ksave_target( VipsImage *in, VipsTarget *target, ... )
__attribute__((sentinel));
/**
* VipsForeignDzLayout:
* @VIPS_FOREIGN_DZ_LAYOUT_DZ: use DeepZoom directory layout

View File

@ -136,11 +136,11 @@ typedef struct _VipsThumbnail {
int heif_thumbnail_width;
int heif_thumbnail_height;
/* For TIFF sources, open subifds to get pyr layers.
/* Pyramids are stored in subifds.
*/
gboolean subifd_pyramid;
/* For TIFF sources, open pages to get pyr layers.
/* Pyramids are stored in pages.
*/
gboolean page_pyramid;
@ -250,18 +250,18 @@ vips_thumbnail_read_header( VipsThumbnail *thumbnail, VipsImage *image )
}
}
/* Detect a TIFF pyramid made of pages following a roughly /2 shrink.
/* Detect a pyramid made of pages following a roughly /2 shrink.
*
* This may not be a pyr tiff, so no error if we can't find the layers.
*/
static void
vips_thumbnail_get_tiff_pyramid_page( VipsThumbnail *thumbnail )
vips_thumbnail_get_pyramid_page( VipsThumbnail *thumbnail )
{
VipsThumbnailClass *class = VIPS_THUMBNAIL_GET_CLASS( thumbnail );
int i;
#ifdef DEBUG
printf( "vips_thumbnail_get_tiff_pyramid_page:\n" );
printf( "vips_thumbnail_get_pyramid_page:\n" );
#endif /*DEBUG*/
/* Single-page docs can't be pyramids.
@ -301,7 +301,7 @@ vips_thumbnail_get_tiff_pyramid_page( VipsThumbnail *thumbnail )
/* Now set level_count. This signals that we've found a pyramid.
*/
#ifdef DEBUG
printf( "vips_thumbnail_get_tiff_pyramid_page: "
printf( "vips_thumbnail_get_pyramid_page: "
"%d layer pyramid detected\n",
thumbnail->n_pages );
#endif /*DEBUG*/
@ -500,7 +500,7 @@ vips_thumbnail_find_jpegshrink( VipsThumbnail *thumbnail,
return( 1 );
}
/* Find the best pyramid (openslide or tiff) level.
/* Find the best pyramid (openslide, tiff, etc.) level.
*/
static int
vips_thumbnail_find_pyrlevel( VipsThumbnail *thumbnail,
@ -553,7 +553,21 @@ vips_thumbnail_open( VipsThumbnail *thumbnail )
thumbnail->subifd_pyramid = FALSE;
thumbnail->page_pyramid = TRUE;
vips_thumbnail_get_tiff_pyramid_page( thumbnail );
vips_thumbnail_get_pyramid_page( thumbnail );
if( thumbnail->level_count == 0 )
thumbnail->page_pyramid = FALSE;
}
}
/* jp2k uses page-based pyramids.
*/
if( vips_isprefix( "VipsForeignLoadJp2k", thumbnail->loader ) ) {
if( thumbnail->level_count == 0 ) {
thumbnail->subifd_pyramid = FALSE;
thumbnail->page_pyramid = TRUE;
vips_thumbnail_get_pyramid_page( thumbnail );
if( thumbnail->level_count == 0 )
thumbnail->page_pyramid = FALSE;
@ -576,8 +590,9 @@ vips_thumbnail_open( VipsThumbnail *thumbnail )
factor = vips_thumbnail_find_jpegshrink( thumbnail,
thumbnail->input_width, thumbnail->input_height );
else if( vips_isprefix( "VipsForeignLoadTiff", thumbnail->loader ) ||
vips_isprefix( "VipsForeignLoadJp2k", thumbnail->loader ) ||
vips_isprefix( "VipsForeignLoadOpenslide",
thumbnail->loader ) ) {
thumbnail->loader ) ) {
if( thumbnail->level_count > 0 )
factor = vips_thumbnail_find_pyrlevel( thumbnail,
thumbnail->input_width,
@ -1112,7 +1127,19 @@ vips_thumbnail_file_open( VipsThumbnail *thumbnail, double factor )
"scale", 1.0 / factor,
NULL ) );
}
else if( vips_isprefix( "VipsForeignLoadJp2k", thumbnail->loader ) ) {
/* jp2k optionally uses page-based pyramids.
*/
if( thumbnail->page_pyramid )
return( vips_image_new_from_file( file->filename,
"access", VIPS_ACCESS_SEQUENTIAL,
"page", (int) factor,
NULL ) );
else
return( vips_image_new_from_file( file->filename,
"access", VIPS_ACCESS_SEQUENTIAL,
NULL ) );
}
else if( vips_isprefix( "VipsForeignLoadTiff", thumbnail->loader ) ) {
/* We support three modes: subifd pyramids, page-based
* pyramids, and simple multi-page TIFFs (no pyramid).
@ -1132,7 +1159,6 @@ vips_thumbnail_file_open( VipsThumbnail *thumbnail, double factor )
"access", VIPS_ACCESS_SEQUENTIAL,
NULL ) );
}
else if( vips_isprefix( "VipsForeignLoadHeif", thumbnail->loader ) ) {
return( vips_image_new_from_file( file->filename,
"access", VIPS_ACCESS_SEQUENTIAL,
@ -1325,6 +1351,23 @@ vips_thumbnail_buffer_open( VipsThumbnail *thumbnail, double factor )
"scale", 1.0 / factor,
NULL ) );
}
else if( vips_isprefix( "VipsForeignLoadJp2k", thumbnail->loader ) ) {
/* Optional page-based pyramids.
*/
if( thumbnail->page_pyramid )
return( vips_image_new_from_buffer(
buffer->buf->data, buffer->buf->length,
buffer->option_string,
"access", VIPS_ACCESS_SEQUENTIAL,
"page", (int) factor,
NULL ) );
else
return( vips_image_new_from_buffer(
buffer->buf->data, buffer->buf->length,
buffer->option_string,
"access", VIPS_ACCESS_SEQUENTIAL,
NULL ) );
}
else if( vips_isprefix( "VipsForeignLoadTiff", thumbnail->loader ) ) {
/* We support three modes: subifd pyramids, page-based
* pyramids, and simple multi-page TIFFs (no pyramid).
@ -1521,6 +1564,23 @@ vips_thumbnail_source_open( VipsThumbnail *thumbnail, double factor )
"scale", 1.0 / factor,
NULL ) );
}
else if( vips_isprefix( "VipsForeignLoadJp2k", thumbnail->loader ) ) {
/* Optional page-based pyramids.
*/
if( thumbnail->page_pyramid )
return( vips_image_new_from_source(
source->source,
source->option_string,
"access", VIPS_ACCESS_SEQUENTIAL,
"page", (int) factor,
NULL ) );
else
return( vips_image_new_from_source(
source->source,
source->option_string,
"access", VIPS_ACCESS_SEQUENTIAL,
NULL ) );
}
else if( vips_isprefix( "VipsForeignLoadTiff", thumbnail->loader ) ) {
/* We support three modes: subifd pyramids, page-based
* pyramids, and simple multi-page TIFFs (no pyramid).

View File

@ -56,6 +56,7 @@ MOSAIC_MARKS = [[489, 140], [66, 141],
MOSAIC_VERTICAL_MARKS = [[388, 44], [364, 346],
[384, 17], [385, 629],
[527, 42], [503, 959]]
JP2K_FILE = os.path.join(IMAGES, "world.jp2")
unsigned_formats = [pyvips.BandFormat.UCHAR,
pyvips.BandFormat.USHORT,

Binary file not shown.

View File

@ -17,7 +17,7 @@ from helpers import \
GIF_ANIM_DISPOSE_PREVIOUS_EXPECTED_PNG_FILE, \
temp_filename, assert_almost_equal_objects, have, skip_if_no, \
TIF1_FILE, TIF2_FILE, TIF4_FILE, WEBP_LOOKS_LIKE_SVG_FILE, \
WEBP_ANIMATED_FILE
WEBP_ANIMATED_FILE, JP2K_FILE
class TestForeign:
tempdir = None
@ -1119,6 +1119,54 @@ class TestForeign:
y = pyvips.Image.new_from_buffer(buf, "")
assert y.get("exif-ifd0-Make").split(" ")[0] == "banana"
@skip_if_no("jp2kload")
def test_jp2kload(self):
def jp2k_valid(im):
a = im(402, 73)
assert_almost_equal_objects(a, [141, 144, 73], threshold=2)
assert im.width == 800
assert im.height == 400
assert im.bands == 3
self.file_loader("jp2kload", JP2K_FILE, jp2k_valid)
self.buffer_loader("jp2kload_buffer", JP2K_FILE, jp2k_valid)
@skip_if_no("jp2ksave")
def test_jp2ksave(self):
self.save_load_buffer("jp2ksave_buffer", "jp2kload_buffer",
self.colour, 80)
buf = self.colour.jp2ksave_buffer(lossless=True)
im2 = pyvips.Image.new_from_buffer(buf, "")
assert (self.colour == im2).min() == 255
# higher Q should mean a bigger buffer
b1 = self.mono.jp2ksave_buffer(Q=10)
b2 = self.mono.jp2ksave_buffer(Q=90)
assert len(b2) > len(b1)
# disabling chroma subsample should mean a bigger buffer
b1 = self.colour.jp2ksave_buffer(subsample_mode="on")
b2 = self.colour.jp2ksave_buffer(subsample_mode="off")
assert len(b2) > len(b1)
# enabling lossless should mean a bigger buffer
b1 = self.colour.jp2ksave_buffer(lossless=False)
b2 = self.colour.jp2ksave_buffer(lossless=True)
assert len(b2) > len(b1)
# 16-bit colour load and save
im = self.colour.colourspace("rgb16")
buf = im.jp2ksave_buffer(lossless=True)
im2 = pyvips.Image.new_from_buffer(buf, "")
assert (im == im2).min() == 255
# openjpeg 32-bit load and save doesn't seem to work, comment out
# im = self.colour.colourspace("rgb16").cast("uint") << 14
# buf = im.jp2ksave_buffer(lossless=True)
# im2 = pyvips.Image.new_from_buffer(buf, "")
# assert (im == im2).min() == 255
if __name__ == '__main__':
pytest.main()