From 9273e441da24e47dff9ae38ef1ef947793a34355 Mon Sep 17 00:00:00 2001 From: Pascal Massimino Date: Sat, 25 Feb 2017 09:10:38 -0800 Subject: [PATCH] fix TIFF encoder regarding rgbA/RGBA Encoder: We were always using ExtraSamples=1, which means associated-alpha. But we don't need the (lossy) excursion to rgbA format. We can save the samples as RGBA directly, by changing ExtraSamples to '2'. The TIFF encoder now checks the colorspace properly, to handle premultiplied format as well as non-premultiplied. Decoder: The result of TIFFReadRGBAImageOriented() is always pre-multiply. So, in case an alpha channel is present, we need to unmultiply it before calling WebPPictureImportRGBA(). See: https://www.itu.int/itudoc/itu-t/com16/tiff-fx/docs/tiff6.pdf (page 31) and also http://www.asmail.be/msg0055469184.html Change-Id: I3258bfdb0eb2e1a53d6c04414f55edb2926c938c --- examples/dwebp.c | 5 ++-- imageio/image_enc.c | 4 ++- imageio/tiffdec.c | 60 ++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 62 insertions(+), 7 deletions(-) diff --git a/examples/dwebp.c b/examples/dwebp.c index ce1bac24..154069a7 100644 --- a/examples/dwebp.c +++ b/examples/dwebp.c @@ -332,9 +332,8 @@ int main(int argc, const char *argv[]) { case BMP: output_buffer->colorspace = bitstream->has_alpha ? MODE_BGRA : MODE_BGR; break; - case TIFF: // note: force pre-multiplied alpha - output_buffer->colorspace = - bitstream->has_alpha ? MODE_rgbA : MODE_RGB; + case TIFF: + output_buffer->colorspace = bitstream->has_alpha ? MODE_RGBA : MODE_RGB; break; case PGM: case RAW_YUV: diff --git a/imageio/image_enc.c b/imageio/image_enc.c index 7c5c64be..0a37785e 100644 --- a/imageio/image_enc.c +++ b/imageio/image_enc.c @@ -361,6 +361,7 @@ int WebPWriteTIFF(FILE* fout, const WebPDecBuffer* const buffer) { const uint8_t* rgba = buffer->u.RGBA.rgba; const int stride = buffer->u.RGBA.stride; const uint8_t bytes_per_px = has_alpha ? 4 : 3; + const int assoc_alpha = WebPIsPremultipliedMode(buffer->colorspace) ? 1 : 2; // For non-alpha case, we omit tag 0x152 (ExtraSamples). const uint8_t num_ifd_entries = has_alpha ? NUM_IFD_ENTRIES : NUM_IFD_ENTRIES - 1; @@ -388,7 +389,8 @@ int WebPWriteTIFF(FILE* fout, const WebPDecBuffer* const buffer) { EXTRA_DATA_OFFSET + 8, 0, 0, 0, 0x1c, 0x01, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, // 154: PlanarConfiguration 0x28, 0x01, 3, 0, 1, 0, 0, 0, 2, 0, 0, 0, // 166: ResolutionUnit (inch) - 0x52, 0x01, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, // 178: ExtraSamples: rgbA + 0x52, 0x01, 3, 0, 1, 0, 0, 0, + assoc_alpha, 0, 0, 0, // 178: ExtraSamples: rgbA/RGBA 0, 0, 0, 0, // 190: IFD terminator // EXTRA_DATA_OFFSET: 8, 0, 8, 0, 8, 0, 8, 0, // BitsPerSample diff --git a/imageio/tiffdec.c b/imageio/tiffdec.c index 46e69dc6..7894864c 100644 --- a/imageio/tiffdec.c +++ b/imageio/tiffdec.c @@ -116,13 +116,48 @@ static tsize_t MyRead(thandle_t opaque, void* dst, tsize_t size) { return size; } +// Unmultiply Argb data. Taken from dsp/alpha_processing +// (we don't want to force a dependency to a libdspdec library). +#define MFIX 24 // 24bit fixed-point arithmetic +#define HALF ((1u << MFIX) >> 1) +#define KINV_255 ((1u << MFIX) / 255u) + +static uint32_t Unmult(uint8_t x, uint32_t mult) { + const uint32_t v = (x * mult + HALF) >> MFIX; + return (v > 255u) ? 255u : v; +} + +static WEBP_INLINE uint32_t GetScale(uint32_t a) { + return (255u << MFIX) / a; +} + +static void MultARGBRow(uint8_t* ptr, int width) { + int x; + for (x = 0; x < width; ++x, ptr += 4) { + const uint32_t alpha = ptr[3]; + if (alpha < 255) { + if (alpha == 0) { // alpha == 0 + ptr[0] = ptr[1] = ptr[2] = 0; + } else { + const uint32_t scale = GetScale(alpha); + ptr[0] = Unmult(ptr[0], scale); + ptr[1] = Unmult(ptr[1], scale); + ptr[2] = Unmult(ptr[2], scale); + } + } + } +} + int ReadTIFF(const uint8_t* const data, size_t data_size, WebPPicture* const pic, int keep_alpha, Metadata* const metadata) { MyData my_data = { data, (toff_t)data_size, 0 }; TIFF* tif; - uint32 width, height; - uint32* raster; + uint32_t width, height; + uint16_t samples_per_px = 0; + uint16_t extra_samples = 0; + uint16_t* extra_samples_ptr = NULL; + uint32_t* raster; int64_t alloc_size; int ok = 0; tdir_t dircount; @@ -143,17 +178,27 @@ int ReadTIFF(const uint8_t* const data, size_t data_size, "Only the first will be used, %d will be ignored.\n", dircount - 1); } + if (!TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLESPERPIXEL, &samples_per_px)) { + fprintf(stderr, "Error! Cannot retrieve TIFF samples-per-pixel info.\n"); + goto End; + } + if (samples_per_px < 3 || samples_per_px > 4) goto End; // not supported if (!(TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width) && TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height))) { fprintf(stderr, "Error! Cannot retrieve TIFF image dimensions.\n"); goto End; } - if (!ImgIoUtilCheckSizeArgumentsOverflow((uint64_t)width * height, sizeof(*raster))) { goto End; } + if (!TIFFGetField(tif, TIFFTAG_EXTRASAMPLES, + &extra_samples, &extra_samples_ptr)) { + fprintf(stderr, "Error! Cannot retrieve TIFF ExtraSamples info.\n"); + goto End; + } + // _Tiffmalloc uses a signed type for size. alloc_size = (int64_t)((uint64_t)width * height * sizeof(*raster)); if (alloc_size < 0 || alloc_size != (tsize_t)alloc_size) goto End; @@ -169,6 +214,15 @@ int ReadTIFF(const uint8_t* const data, size_t data_size, #ifdef WORDS_BIGENDIAN TIFFSwabArrayOfLong(raster, width * height); #endif + // if we have an alpha channel, we must un-multiply from rgbA to RGBA + if (samples_per_px > 3 && extra_samples == 1) { + uint32_t y; + uint8_t* tmp = (uint8_t*)raster; + for (y = 0; y < height; ++y) { + MultARGBRow(tmp, width); + tmp += stride; + } + } ok = keep_alpha ? WebPPictureImportRGBA(pic, (const uint8_t*)raster, stride) : WebPPictureImportRGBX(pic, (const uint8_t*)raster, stride);