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
|
||||
- move openslide, libheif, poppler and magick to loadable modules [kleisauke]
|
||||
- better detection of invalid ICC profiles, better fallback paths
|
||||
- add "premultiply" flag to tiffsave
|
||||
|
||||
22/12/20 start 8.10.6
|
||||
- don't seek on bad file descriptors [kleisauke]
|
||||
|
@ -64,7 +64,8 @@ int vips__tiff_write( VipsImage *in, const char *filename,
|
||||
int level,
|
||||
gboolean lossless,
|
||||
VipsForeignDzDepth depth,
|
||||
gboolean subifd );
|
||||
gboolean subifd,
|
||||
gboolean premultiply );
|
||||
|
||||
int vips__tiff_write_buf( VipsImage *in,
|
||||
void **obuf, size_t *olen,
|
||||
@ -83,7 +84,8 @@ int vips__tiff_write_buf( VipsImage *in,
|
||||
int level,
|
||||
gboolean lossless,
|
||||
VipsForeignDzDepth depth,
|
||||
gboolean subifd );
|
||||
gboolean subifd,
|
||||
gboolean premultiply );
|
||||
|
||||
gboolean vips__istiff_source( VipsSource *source );
|
||||
gboolean vips__istifftiled_source( VipsSource *source );
|
||||
|
@ -26,6 +26,8 @@
|
||||
* 8/6/20
|
||||
* - add bitdepth support for 2 and 4 bit greyscale images
|
||||
* - deprecate "squash"
|
||||
* 1/5/21
|
||||
* - add "premultiply" flag
|
||||
*/
|
||||
|
||||
/*
|
||||
@ -105,6 +107,7 @@ typedef struct _VipsForeignSaveTiff {
|
||||
gboolean lossless;
|
||||
VipsForeignDzDepth depth;
|
||||
gboolean subifd;
|
||||
gboolean premultiply;
|
||||
|
||||
} VipsForeignSaveTiff;
|
||||
|
||||
@ -341,13 +344,20 @@ vips_foreign_save_tiff_class_init( VipsForeignSaveTiffClass *class )
|
||||
G_STRUCT_OFFSET( VipsForeignSaveTiff, depth ),
|
||||
VIPS_TYPE_FOREIGN_DZ_DEPTH, VIPS_FOREIGN_DZ_DEPTH_ONETILE );
|
||||
|
||||
VIPS_ARG_BOOL( class, "subifd", 24,
|
||||
VIPS_ARG_BOOL( class, "subifd", 26,
|
||||
_( "Sub-IFD" ),
|
||||
_( "Save pyr layers as sub-IFDs" ),
|
||||
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||
G_STRUCT_OFFSET( VipsForeignSaveTiff, subifd ),
|
||||
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,
|
||||
_( "RGB JPEG" ),
|
||||
_( "Output RGB JPEG rather than YCbCr" ),
|
||||
@ -427,7 +437,8 @@ vips_foreign_save_tiff_file_build( VipsObject *object )
|
||||
tiff->level,
|
||||
tiff->lossless,
|
||||
tiff->depth,
|
||||
tiff->subifd ) )
|
||||
tiff->subifd,
|
||||
tiff->premultiply ) )
|
||||
return( -1 );
|
||||
|
||||
return( 0 );
|
||||
@ -500,7 +511,8 @@ vips_foreign_save_tiff_buffer_build( VipsObject *object )
|
||||
tiff->level,
|
||||
tiff->lossless,
|
||||
tiff->depth,
|
||||
tiff->subifd ) )
|
||||
tiff->subifd,
|
||||
tiff->premultiply ) )
|
||||
return( -1 );
|
||||
|
||||
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
|
||||
* * @depth: #VipsForeignDzDepth how deep to make the pyramid
|
||||
* * @subifd: %gboolean write pyr layers as sub-ifds
|
||||
* * @premultiply: %gboolean write premultiplied alpha
|
||||
*
|
||||
* 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.
|
||||
* 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().
|
||||
*
|
||||
* Returns: 0 on success, -1 on error.
|
||||
@ -704,6 +720,7 @@ vips_tiffsave( VipsImage *in, const char *filename, ... )
|
||||
* * @lossless: %gboolean, WebP losssless mode
|
||||
* * @depth: #VipsForeignDzDepth how deep to make the pyramid
|
||||
* * @subifd: %gboolean write pyr layers as sub-ifds
|
||||
* * @premultiply: %gboolean write premultiplied alpha
|
||||
*
|
||||
* As vips_tiffsave(), but save to a memory buffer.
|
||||
*
|
||||
|
@ -364,6 +364,7 @@ struct _Wtiff {
|
||||
gboolean lossless; /* lossless mode */
|
||||
VipsForeignDzDepth depth; /* Pyr depth */
|
||||
gboolean subifd; /* Write pyr layers into subifds */
|
||||
gboolean premultiply; /* Premultiply alpha */
|
||||
|
||||
/* 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
|
||||
@ -818,9 +819,14 @@ wtiff_write_header( Wtiff *wtiff, Layer *layer )
|
||||
/* EXTRASAMPLE_UNASSALPHA means generic extra
|
||||
* alpha-like channels. ASSOCALPHA means pre-multipled
|
||||
* alpha only.
|
||||
*
|
||||
* Make the first channel the premultiplied alpha, if
|
||||
* we are premultiplying.
|
||||
*/
|
||||
for( i = 0; i < alpha_bands; i++ )
|
||||
v[i] = EXTRASAMPLE_UNASSALPHA;
|
||||
v[i] = i == 0 && wtiff->premultiply ?
|
||||
EXTRASAMPLE_ASSOCALPHA :
|
||||
EXTRASAMPLE_UNASSALPHA;
|
||||
TIFFSetField( tif,
|
||||
TIFFTAG_EXTRASAMPLES, alpha_bands, v );
|
||||
}
|
||||
@ -1083,24 +1089,56 @@ get_resunit( VipsForeignTiffResunit resunit )
|
||||
static int
|
||||
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 );
|
||||
}
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
if( wtiff->bitdepth &&
|
||||
wtiff->input->Bands == 3 &&
|
||||
wtiff->input->BandFmt == VIPS_FORMAT_FLOAT &&
|
||||
wtiff->input->Type == VIPS_INTERPRETATION_LAB ) {
|
||||
if( vips_Lab2LabQ( wtiff->input, &wtiff->ready, NULL ) )
|
||||
input->Bands == 3 &&
|
||||
input->BandFmt == VIPS_FORMAT_FLOAT &&
|
||||
input->Type == VIPS_INTERPRETATION_LAB ) {
|
||||
if( vips_Lab2LabQ( input, &x, NULL ) ) {
|
||||
VIPS_UNREF( input );
|
||||
return( -1 );
|
||||
wtiff->bitdepth = 0;
|
||||
}
|
||||
else {
|
||||
wtiff->ready = wtiff->input;
|
||||
g_object_ref( wtiff->ready );
|
||||
}
|
||||
VIPS_UNREF( input );
|
||||
input = x;
|
||||
}
|
||||
|
||||
wtiff->ready = input;
|
||||
|
||||
return( 0 );
|
||||
}
|
||||
|
||||
@ -1122,7 +1160,8 @@ wtiff_new( VipsImage *input, const char *filename,
|
||||
int level,
|
||||
gboolean lossless,
|
||||
VipsForeignDzDepth depth,
|
||||
gboolean subifd )
|
||||
gboolean subifd,
|
||||
gboolean premultiply )
|
||||
{
|
||||
Wtiff *wtiff;
|
||||
|
||||
@ -1155,6 +1194,7 @@ wtiff_new( VipsImage *input, const char *filename,
|
||||
wtiff->lossless = lossless;
|
||||
wtiff->depth = depth;
|
||||
wtiff->subifd = subifd;
|
||||
wtiff->premultiply = premultiply;
|
||||
wtiff->toilet_roll = FALSE;
|
||||
wtiff->page_height = vips_image_get_page_height( input );
|
||||
wtiff->page_number = 0;
|
||||
@ -2176,7 +2216,8 @@ vips__tiff_write( VipsImage *input, const char *filename,
|
||||
int level,
|
||||
gboolean lossless,
|
||||
VipsForeignDzDepth depth,
|
||||
gboolean subifd )
|
||||
gboolean subifd,
|
||||
gboolean premultiply )
|
||||
{
|
||||
Wtiff *wtiff;
|
||||
|
||||
@ -2191,7 +2232,7 @@ vips__tiff_write( VipsImage *input, const char *filename,
|
||||
tile, tile_width, tile_height, pyramid, bitdepth,
|
||||
miniswhite, resunit, xres, yres, bigtiff, rgbjpeg,
|
||||
properties, strip, region_shrink, level, lossless, depth,
|
||||
subifd )) )
|
||||
subifd, premultiply )) )
|
||||
return( -1 );
|
||||
|
||||
if( wtiff_write_image( wtiff ) ) {
|
||||
@ -2222,7 +2263,8 @@ vips__tiff_write_buf( VipsImage *input,
|
||||
int level,
|
||||
gboolean lossless,
|
||||
VipsForeignDzDepth depth,
|
||||
gboolean subifd )
|
||||
gboolean subifd,
|
||||
gboolean premultiply )
|
||||
{
|
||||
Wtiff *wtiff;
|
||||
|
||||
@ -2233,7 +2275,7 @@ vips__tiff_write_buf( VipsImage *input,
|
||||
tile, tile_width, tile_height, pyramid, bitdepth,
|
||||
miniswhite, resunit, xres, yres, bigtiff, rgbjpeg,
|
||||
properties, strip, region_shrink, level, lossless, depth,
|
||||
subifd )) )
|
||||
subifd, premultiply )) )
|
||||
return( -1 );
|
||||
|
||||
wtiff->obuf = obuf;
|
||||
|
@ -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, JP2K_FILE
|
||||
WEBP_ANIMATED_FILE, JP2K_FILE, RGBA_FILE
|
||||
|
||||
class TestForeign:
|
||||
tempdir = None
|
||||
@ -27,6 +27,7 @@ class TestForeign:
|
||||
cls.tempdir = tempfile.mkdtemp()
|
||||
|
||||
cls.colour = pyvips.Image.jpegload(JPEG_FILE)
|
||||
cls.rgba = pyvips.Image.new_from_file(RGBA_FILE)
|
||||
cls.mono = cls.colour.extract_band(1).copy()
|
||||
# we remove the ICC profile: the RGB one will no longer be appropriate
|
||||
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.colour)
|
||||
self.save_load("%s.tif", self.cmyk)
|
||||
|
||||
self.save_load("%s.tif", self.rgba)
|
||||
self.save_load("%s.tif", self.onebit)
|
||||
|
||||
self.save_load_file(".tif", "[bitdepth=1]", 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",
|
||||
"[profile={0}]".format(SRGB_FILE),
|
||||
self.colour)
|
||||
self.save_load_file(".tif", f"[profile={SRGB_FILE}]", 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,subifd]", self.colour)
|
||||
@ -510,6 +510,12 @@ class TestForeign:
|
||||
buf2 = f.read()
|
||||
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)
|
||||
b = pyvips.Image.new_from_buffer(buf2, "", page=2)
|
||||
assert a.width == b.width
|
||||
|
Loading…
Reference in New Issue
Block a user