add "premultiply" param to tiffsave
Some programs, like indesign, only work with premultiplied alpha in TIFF. To make TIFFs which are compatible with these programs, we'll need an extra TIFF save flag (perhaps premultiply?) to premultiply alpha and save as EXTRASAMPLE_ASSOCALPHA. see https://github.com/libvips/libvips/issues/2192
This commit is contained in:
parent
33812d79ad
commit
03f76b73b4
@ -28,6 +28,7 @@
|
|||||||
- add "rgba" flag to vips_text() to enable full colour text rendering
|
- add "rgba" flag to vips_text() to enable full colour text rendering
|
||||||
- move openslide, libheif, poppler and magick to loadable modules [kleisauke]
|
- move openslide, libheif, poppler and magick to loadable modules [kleisauke]
|
||||||
- better detection of invalid ICC profiles, better fallback paths
|
- better detection of invalid ICC profiles, better fallback paths
|
||||||
|
- add "premultiply" flag to tiffsave
|
||||||
|
|
||||||
22/12/20 start 8.10.6
|
22/12/20 start 8.10.6
|
||||||
- don't seek on bad file descriptors [kleisauke]
|
- don't seek on bad file descriptors [kleisauke]
|
||||||
|
@ -64,7 +64,8 @@ int vips__tiff_write( VipsImage *in, const char *filename,
|
|||||||
int level,
|
int level,
|
||||||
gboolean lossless,
|
gboolean lossless,
|
||||||
VipsForeignDzDepth depth,
|
VipsForeignDzDepth depth,
|
||||||
gboolean subifd );
|
gboolean subifd,
|
||||||
|
gboolean premultiply );
|
||||||
|
|
||||||
int vips__tiff_write_buf( VipsImage *in,
|
int vips__tiff_write_buf( VipsImage *in,
|
||||||
void **obuf, size_t *olen,
|
void **obuf, size_t *olen,
|
||||||
@ -83,7 +84,8 @@ int vips__tiff_write_buf( VipsImage *in,
|
|||||||
int level,
|
int level,
|
||||||
gboolean lossless,
|
gboolean lossless,
|
||||||
VipsForeignDzDepth depth,
|
VipsForeignDzDepth depth,
|
||||||
gboolean subifd );
|
gboolean subifd,
|
||||||
|
gboolean premultiply );
|
||||||
|
|
||||||
gboolean vips__istiff_source( VipsSource *source );
|
gboolean vips__istiff_source( VipsSource *source );
|
||||||
gboolean vips__istifftiled_source( VipsSource *source );
|
gboolean vips__istifftiled_source( VipsSource *source );
|
||||||
|
@ -26,6 +26,8 @@
|
|||||||
* 8/6/20
|
* 8/6/20
|
||||||
* - add bitdepth support for 2 and 4 bit greyscale images
|
* - add bitdepth support for 2 and 4 bit greyscale images
|
||||||
* - deprecate "squash"
|
* - deprecate "squash"
|
||||||
|
* 1/5/21
|
||||||
|
* - add "premultiply" flag
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -105,6 +107,7 @@ typedef struct _VipsForeignSaveTiff {
|
|||||||
gboolean lossless;
|
gboolean lossless;
|
||||||
VipsForeignDzDepth depth;
|
VipsForeignDzDepth depth;
|
||||||
gboolean subifd;
|
gboolean subifd;
|
||||||
|
gboolean premultiply;
|
||||||
|
|
||||||
} VipsForeignSaveTiff;
|
} VipsForeignSaveTiff;
|
||||||
|
|
||||||
@ -341,13 +344,20 @@ vips_foreign_save_tiff_class_init( VipsForeignSaveTiffClass *class )
|
|||||||
G_STRUCT_OFFSET( VipsForeignSaveTiff, depth ),
|
G_STRUCT_OFFSET( VipsForeignSaveTiff, depth ),
|
||||||
VIPS_TYPE_FOREIGN_DZ_DEPTH, VIPS_FOREIGN_DZ_DEPTH_ONETILE );
|
VIPS_TYPE_FOREIGN_DZ_DEPTH, VIPS_FOREIGN_DZ_DEPTH_ONETILE );
|
||||||
|
|
||||||
VIPS_ARG_BOOL( class, "subifd", 24,
|
VIPS_ARG_BOOL( class, "subifd", 26,
|
||||||
_( "Sub-IFD" ),
|
_( "Sub-IFD" ),
|
||||||
_( "Save pyr layers as sub-IFDs" ),
|
_( "Save pyr layers as sub-IFDs" ),
|
||||||
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||||
G_STRUCT_OFFSET( VipsForeignSaveTiff, subifd ),
|
G_STRUCT_OFFSET( VipsForeignSaveTiff, subifd ),
|
||||||
FALSE );
|
FALSE );
|
||||||
|
|
||||||
|
VIPS_ARG_BOOL( class, "premultiply", 27,
|
||||||
|
_( "Premultiply" ),
|
||||||
|
_( "Save with premultiplied alpha" ),
|
||||||
|
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||||
|
G_STRUCT_OFFSET( VipsForeignSaveTiff, premultiply ),
|
||||||
|
FALSE );
|
||||||
|
|
||||||
VIPS_ARG_BOOL( class, "rgbjpeg", 20,
|
VIPS_ARG_BOOL( class, "rgbjpeg", 20,
|
||||||
_( "RGB JPEG" ),
|
_( "RGB JPEG" ),
|
||||||
_( "Output RGB JPEG rather than YCbCr" ),
|
_( "Output RGB JPEG rather than YCbCr" ),
|
||||||
@ -427,7 +437,8 @@ vips_foreign_save_tiff_file_build( VipsObject *object )
|
|||||||
tiff->level,
|
tiff->level,
|
||||||
tiff->lossless,
|
tiff->lossless,
|
||||||
tiff->depth,
|
tiff->depth,
|
||||||
tiff->subifd ) )
|
tiff->subifd,
|
||||||
|
tiff->premultiply ) )
|
||||||
return( -1 );
|
return( -1 );
|
||||||
|
|
||||||
return( 0 );
|
return( 0 );
|
||||||
@ -500,7 +511,8 @@ vips_foreign_save_tiff_buffer_build( VipsObject *object )
|
|||||||
tiff->level,
|
tiff->level,
|
||||||
tiff->lossless,
|
tiff->lossless,
|
||||||
tiff->depth,
|
tiff->depth,
|
||||||
tiff->subifd ) )
|
tiff->subifd,
|
||||||
|
tiff->premultiply ) )
|
||||||
return( -1 );
|
return( -1 );
|
||||||
|
|
||||||
blob = vips_blob_new( (VipsCallbackFn) vips_area_free_cb, obuf, olen );
|
blob = vips_blob_new( (VipsCallbackFn) vips_area_free_cb, obuf, olen );
|
||||||
@ -567,6 +579,7 @@ vips_foreign_save_tiff_buffer_init( VipsForeignSaveTiffBuffer *buffer )
|
|||||||
* * @lossless: %gboolean, WebP losssless mode
|
* * @lossless: %gboolean, WebP losssless mode
|
||||||
* * @depth: #VipsForeignDzDepth how deep to make the pyramid
|
* * @depth: #VipsForeignDzDepth how deep to make the pyramid
|
||||||
* * @subifd: %gboolean write pyr layers as sub-ifds
|
* * @subifd: %gboolean write pyr layers as sub-ifds
|
||||||
|
* * @premultiply: %gboolean write premultiplied alpha
|
||||||
*
|
*
|
||||||
* Write a VIPS image to a file as TIFF.
|
* Write a VIPS image to a file as TIFF.
|
||||||
*
|
*
|
||||||
@ -658,6 +671,9 @@ vips_foreign_save_tiff_buffer_init( VipsForeignSaveTiffBuffer *buffer )
|
|||||||
* Set @subifd to save pyramid layers as sub-directories of the main image.
|
* Set @subifd to save pyramid layers as sub-directories of the main image.
|
||||||
* Setting this option can improve compatibility with formats like OME.
|
* Setting this option can improve compatibility with formats like OME.
|
||||||
*
|
*
|
||||||
|
* Set @premultiply tio save with premultiplied alpha. Some programs, such as
|
||||||
|
* InDesign, will only work with premultiplied alpha.
|
||||||
|
*
|
||||||
* See also: vips_tiffload(), vips_image_write_to_file().
|
* See also: vips_tiffload(), vips_image_write_to_file().
|
||||||
*
|
*
|
||||||
* Returns: 0 on success, -1 on error.
|
* Returns: 0 on success, -1 on error.
|
||||||
@ -704,6 +720,7 @@ vips_tiffsave( VipsImage *in, const char *filename, ... )
|
|||||||
* * @lossless: %gboolean, WebP losssless mode
|
* * @lossless: %gboolean, WebP losssless mode
|
||||||
* * @depth: #VipsForeignDzDepth how deep to make the pyramid
|
* * @depth: #VipsForeignDzDepth how deep to make the pyramid
|
||||||
* * @subifd: %gboolean write pyr layers as sub-ifds
|
* * @subifd: %gboolean write pyr layers as sub-ifds
|
||||||
|
* * @premultiply: %gboolean write premultiplied alpha
|
||||||
*
|
*
|
||||||
* As vips_tiffsave(), but save to a memory buffer.
|
* As vips_tiffsave(), but save to a memory buffer.
|
||||||
*
|
*
|
||||||
|
@ -364,6 +364,7 @@ struct _Wtiff {
|
|||||||
gboolean lossless; /* lossless mode */
|
gboolean lossless; /* lossless mode */
|
||||||
VipsForeignDzDepth depth; /* Pyr depth */
|
VipsForeignDzDepth depth; /* Pyr depth */
|
||||||
gboolean subifd; /* Write pyr layers into subifds */
|
gboolean subifd; /* Write pyr layers into subifds */
|
||||||
|
gboolean premultiply; /* Premultiply alpha */
|
||||||
|
|
||||||
/* True if we've detected a toilet-roll image, plus the page height,
|
/* True if we've detected a toilet-roll image, plus the page height,
|
||||||
* which has been checked to be a factor of im->Ysize. page_number
|
* which has been checked to be a factor of im->Ysize. page_number
|
||||||
@ -818,9 +819,14 @@ wtiff_write_header( Wtiff *wtiff, Layer *layer )
|
|||||||
/* EXTRASAMPLE_UNASSALPHA means generic extra
|
/* EXTRASAMPLE_UNASSALPHA means generic extra
|
||||||
* alpha-like channels. ASSOCALPHA means pre-multipled
|
* alpha-like channels. ASSOCALPHA means pre-multipled
|
||||||
* alpha only.
|
* alpha only.
|
||||||
|
*
|
||||||
|
* Make the first channel the premultiplied alpha, if
|
||||||
|
* we are premultiplying.
|
||||||
*/
|
*/
|
||||||
for( i = 0; i < alpha_bands; i++ )
|
for( i = 0; i < alpha_bands; i++ )
|
||||||
v[i] = EXTRASAMPLE_UNASSALPHA;
|
v[i] = i == 0 && wtiff->premultiply ?
|
||||||
|
EXTRASAMPLE_ASSOCALPHA :
|
||||||
|
EXTRASAMPLE_UNASSALPHA;
|
||||||
TIFFSetField( tif,
|
TIFFSetField( tif,
|
||||||
TIFFTAG_EXTRASAMPLES, alpha_bands, v );
|
TIFFTAG_EXTRASAMPLES, alpha_bands, v );
|
||||||
}
|
}
|
||||||
@ -1083,24 +1089,56 @@ get_resunit( VipsForeignTiffResunit resunit )
|
|||||||
static int
|
static int
|
||||||
ready_to_write( Wtiff *wtiff )
|
ready_to_write( Wtiff *wtiff )
|
||||||
{
|
{
|
||||||
if( vips_check_coding_known( "vips2tiff", wtiff->input ) )
|
VipsImage *input;
|
||||||
|
VipsImage *x;
|
||||||
|
|
||||||
|
input = wtiff->input;
|
||||||
|
g_object_ref( input );
|
||||||
|
|
||||||
|
if( vips_check_coding_known( "vips2tiff", input ) ) {
|
||||||
|
VIPS_UNREF( input );
|
||||||
return( -1 );
|
return( -1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Premultiply any alpha, if necessary.
|
||||||
|
*/
|
||||||
|
if( wtiff->premultiply &&
|
||||||
|
vips_image_hasalpha( input ) ) {
|
||||||
|
VipsBandFormat start_format = input->BandFmt;
|
||||||
|
|
||||||
|
if( vips_premultiply( input, &x, NULL ) ) {
|
||||||
|
VIPS_UNREF( input );
|
||||||
|
return( -1 );
|
||||||
|
}
|
||||||
|
VIPS_UNREF( input );
|
||||||
|
input = x;
|
||||||
|
|
||||||
|
/* Premultiply always makes a float -- cast back again.
|
||||||
|
*/
|
||||||
|
if( vips_cast( input, &x, start_format, NULL ) ) {
|
||||||
|
VIPS_UNREF( input );
|
||||||
|
return( -1 );
|
||||||
|
}
|
||||||
|
VIPS_UNREF( input );
|
||||||
|
input = x;
|
||||||
|
}
|
||||||
|
|
||||||
/* "squash" float LAB down to LABQ.
|
/* "squash" float LAB down to LABQ.
|
||||||
*/
|
*/
|
||||||
if( wtiff->bitdepth &&
|
if( wtiff->bitdepth &&
|
||||||
wtiff->input->Bands == 3 &&
|
input->Bands == 3 &&
|
||||||
wtiff->input->BandFmt == VIPS_FORMAT_FLOAT &&
|
input->BandFmt == VIPS_FORMAT_FLOAT &&
|
||||||
wtiff->input->Type == VIPS_INTERPRETATION_LAB ) {
|
input->Type == VIPS_INTERPRETATION_LAB ) {
|
||||||
if( vips_Lab2LabQ( wtiff->input, &wtiff->ready, NULL ) )
|
if( vips_Lab2LabQ( input, &x, NULL ) ) {
|
||||||
|
VIPS_UNREF( input );
|
||||||
return( -1 );
|
return( -1 );
|
||||||
wtiff->bitdepth = 0;
|
|
||||||
}
|
}
|
||||||
else {
|
VIPS_UNREF( input );
|
||||||
wtiff->ready = wtiff->input;
|
input = x;
|
||||||
g_object_ref( wtiff->ready );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wtiff->ready = input;
|
||||||
|
|
||||||
return( 0 );
|
return( 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1122,7 +1160,8 @@ wtiff_new( VipsImage *input, const char *filename,
|
|||||||
int level,
|
int level,
|
||||||
gboolean lossless,
|
gboolean lossless,
|
||||||
VipsForeignDzDepth depth,
|
VipsForeignDzDepth depth,
|
||||||
gboolean subifd )
|
gboolean subifd,
|
||||||
|
gboolean premultiply )
|
||||||
{
|
{
|
||||||
Wtiff *wtiff;
|
Wtiff *wtiff;
|
||||||
|
|
||||||
@ -1155,6 +1194,7 @@ wtiff_new( VipsImage *input, const char *filename,
|
|||||||
wtiff->lossless = lossless;
|
wtiff->lossless = lossless;
|
||||||
wtiff->depth = depth;
|
wtiff->depth = depth;
|
||||||
wtiff->subifd = subifd;
|
wtiff->subifd = subifd;
|
||||||
|
wtiff->premultiply = premultiply;
|
||||||
wtiff->toilet_roll = FALSE;
|
wtiff->toilet_roll = FALSE;
|
||||||
wtiff->page_height = vips_image_get_page_height( input );
|
wtiff->page_height = vips_image_get_page_height( input );
|
||||||
wtiff->page_number = 0;
|
wtiff->page_number = 0;
|
||||||
@ -2176,7 +2216,8 @@ vips__tiff_write( VipsImage *input, const char *filename,
|
|||||||
int level,
|
int level,
|
||||||
gboolean lossless,
|
gboolean lossless,
|
||||||
VipsForeignDzDepth depth,
|
VipsForeignDzDepth depth,
|
||||||
gboolean subifd )
|
gboolean subifd,
|
||||||
|
gboolean premultiply )
|
||||||
{
|
{
|
||||||
Wtiff *wtiff;
|
Wtiff *wtiff;
|
||||||
|
|
||||||
@ -2191,7 +2232,7 @@ vips__tiff_write( VipsImage *input, const char *filename,
|
|||||||
tile, tile_width, tile_height, pyramid, bitdepth,
|
tile, tile_width, tile_height, pyramid, bitdepth,
|
||||||
miniswhite, resunit, xres, yres, bigtiff, rgbjpeg,
|
miniswhite, resunit, xres, yres, bigtiff, rgbjpeg,
|
||||||
properties, strip, region_shrink, level, lossless, depth,
|
properties, strip, region_shrink, level, lossless, depth,
|
||||||
subifd )) )
|
subifd, premultiply )) )
|
||||||
return( -1 );
|
return( -1 );
|
||||||
|
|
||||||
if( wtiff_write_image( wtiff ) ) {
|
if( wtiff_write_image( wtiff ) ) {
|
||||||
@ -2222,7 +2263,8 @@ vips__tiff_write_buf( VipsImage *input,
|
|||||||
int level,
|
int level,
|
||||||
gboolean lossless,
|
gboolean lossless,
|
||||||
VipsForeignDzDepth depth,
|
VipsForeignDzDepth depth,
|
||||||
gboolean subifd )
|
gboolean subifd,
|
||||||
|
gboolean premultiply )
|
||||||
{
|
{
|
||||||
Wtiff *wtiff;
|
Wtiff *wtiff;
|
||||||
|
|
||||||
@ -2233,7 +2275,7 @@ vips__tiff_write_buf( VipsImage *input,
|
|||||||
tile, tile_width, tile_height, pyramid, bitdepth,
|
tile, tile_width, tile_height, pyramid, bitdepth,
|
||||||
miniswhite, resunit, xres, yres, bigtiff, rgbjpeg,
|
miniswhite, resunit, xres, yres, bigtiff, rgbjpeg,
|
||||||
properties, strip, region_shrink, level, lossless, depth,
|
properties, strip, region_shrink, level, lossless, depth,
|
||||||
subifd )) )
|
subifd, premultiply )) )
|
||||||
return( -1 );
|
return( -1 );
|
||||||
|
|
||||||
wtiff->obuf = obuf;
|
wtiff->obuf = obuf;
|
||||||
|
@ -17,7 +17,7 @@ from helpers import \
|
|||||||
GIF_ANIM_DISPOSE_PREVIOUS_EXPECTED_PNG_FILE, \
|
GIF_ANIM_DISPOSE_PREVIOUS_EXPECTED_PNG_FILE, \
|
||||||
temp_filename, assert_almost_equal_objects, have, skip_if_no, \
|
temp_filename, assert_almost_equal_objects, have, skip_if_no, \
|
||||||
TIF1_FILE, TIF2_FILE, TIF4_FILE, WEBP_LOOKS_LIKE_SVG_FILE, \
|
TIF1_FILE, TIF2_FILE, TIF4_FILE, WEBP_LOOKS_LIKE_SVG_FILE, \
|
||||||
WEBP_ANIMATED_FILE, JP2K_FILE
|
WEBP_ANIMATED_FILE, JP2K_FILE, RGBA_FILE
|
||||||
|
|
||||||
class TestForeign:
|
class TestForeign:
|
||||||
tempdir = None
|
tempdir = None
|
||||||
@ -27,6 +27,7 @@ class TestForeign:
|
|||||||
cls.tempdir = tempfile.mkdtemp()
|
cls.tempdir = tempfile.mkdtemp()
|
||||||
|
|
||||||
cls.colour = pyvips.Image.jpegload(JPEG_FILE)
|
cls.colour = pyvips.Image.jpegload(JPEG_FILE)
|
||||||
|
cls.rgba = pyvips.Image.new_from_file(RGBA_FILE)
|
||||||
cls.mono = cls.colour.extract_band(1).copy()
|
cls.mono = cls.colour.extract_band(1).copy()
|
||||||
# we remove the ICC profile: the RGB one will no longer be appropriate
|
# we remove the ICC profile: the RGB one will no longer be appropriate
|
||||||
cls.mono.remove("icc-profile-data")
|
cls.mono.remove("icc-profile-data")
|
||||||
@ -387,15 +388,14 @@ class TestForeign:
|
|||||||
self.save_load("%s.tif", self.mono)
|
self.save_load("%s.tif", self.mono)
|
||||||
self.save_load("%s.tif", self.colour)
|
self.save_load("%s.tif", self.colour)
|
||||||
self.save_load("%s.tif", self.cmyk)
|
self.save_load("%s.tif", self.cmyk)
|
||||||
|
self.save_load("%s.tif", self.rgba)
|
||||||
self.save_load("%s.tif", self.onebit)
|
self.save_load("%s.tif", self.onebit)
|
||||||
|
|
||||||
self.save_load_file(".tif", "[bitdepth=1]", self.onebit)
|
self.save_load_file(".tif", "[bitdepth=1]", self.onebit)
|
||||||
self.save_load_file(".tif", "[miniswhite]", self.onebit)
|
self.save_load_file(".tif", "[miniswhite]", self.onebit)
|
||||||
self.save_load_file(".tif", "[bitdepth=1,miniswhite]", self.onebit)
|
self.save_load_file(".tif", "[bitdepth=1,miniswhite]", self.onebit)
|
||||||
|
|
||||||
self.save_load_file(".tif",
|
self.save_load_file(".tif", f"[profile={SRGB_FILE}]", self.colour)
|
||||||
"[profile={0}]".format(SRGB_FILE),
|
|
||||||
self.colour)
|
|
||||||
self.save_load_file(".tif", "[tile]", self.colour)
|
self.save_load_file(".tif", "[tile]", self.colour)
|
||||||
self.save_load_file(".tif", "[tile,pyramid]", self.colour)
|
self.save_load_file(".tif", "[tile,pyramid]", self.colour)
|
||||||
self.save_load_file(".tif", "[tile,pyramid,subifd]", self.colour)
|
self.save_load_file(".tif", "[tile,pyramid,subifd]", self.colour)
|
||||||
@ -510,6 +510,12 @@ class TestForeign:
|
|||||||
buf2 = f.read()
|
buf2 = f.read()
|
||||||
assert len(buf) == len(buf2)
|
assert len(buf) == len(buf2)
|
||||||
|
|
||||||
|
filename = temp_filename(self.tempdir, '.tif')
|
||||||
|
self.rgba.write_to_file(filename, premultiply=True)
|
||||||
|
a = pyvips.Image.new_from_file(filename)
|
||||||
|
b = self.rgba.premultiply().cast("uchar").unpremultiply().cast("uchar")
|
||||||
|
assert a.avg() == b.avg()
|
||||||
|
|
||||||
a = pyvips.Image.new_from_buffer(buf, "", page=2)
|
a = pyvips.Image.new_from_buffer(buf, "", page=2)
|
||||||
b = pyvips.Image.new_from_buffer(buf2, "", page=2)
|
b = pyvips.Image.new_from_buffer(buf2, "", page=2)
|
||||||
assert a.width == b.width
|
assert a.width == b.width
|
||||||
|
Loading…
Reference in New Issue
Block a user