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
This commit is contained in:
Pascal Massimino 2017-02-25 09:10:38 -08:00
parent 17e3c11f99
commit 9273e441da
3 changed files with 62 additions and 7 deletions

View File

@ -332,9 +332,8 @@ int main(int argc, const char *argv[]) {
case BMP: case BMP:
output_buffer->colorspace = bitstream->has_alpha ? MODE_BGRA : MODE_BGR; output_buffer->colorspace = bitstream->has_alpha ? MODE_BGRA : MODE_BGR;
break; break;
case TIFF: // note: force pre-multiplied alpha case TIFF:
output_buffer->colorspace = output_buffer->colorspace = bitstream->has_alpha ? MODE_RGBA : MODE_RGB;
bitstream->has_alpha ? MODE_rgbA : MODE_RGB;
break; break;
case PGM: case PGM:
case RAW_YUV: case RAW_YUV:

View File

@ -361,6 +361,7 @@ int WebPWriteTIFF(FILE* fout, const WebPDecBuffer* const buffer) {
const uint8_t* rgba = buffer->u.RGBA.rgba; const uint8_t* rgba = buffer->u.RGBA.rgba;
const int stride = buffer->u.RGBA.stride; const int stride = buffer->u.RGBA.stride;
const uint8_t bytes_per_px = has_alpha ? 4 : 3; 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). // For non-alpha case, we omit tag 0x152 (ExtraSamples).
const uint8_t num_ifd_entries = has_alpha ? NUM_IFD_ENTRIES const uint8_t num_ifd_entries = has_alpha ? NUM_IFD_ENTRIES
: NUM_IFD_ENTRIES - 1; : NUM_IFD_ENTRIES - 1;
@ -388,7 +389,8 @@ int WebPWriteTIFF(FILE* fout, const WebPDecBuffer* const buffer) {
EXTRA_DATA_OFFSET + 8, 0, 0, 0, EXTRA_DATA_OFFSET + 8, 0, 0, 0,
0x1c, 0x01, 3, 0, 1, 0, 0, 0, 1, 0, 0, 0, // 154: PlanarConfiguration 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) 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 0, 0, 0, 0, // 190: IFD terminator
// EXTRA_DATA_OFFSET: // EXTRA_DATA_OFFSET:
8, 0, 8, 0, 8, 0, 8, 0, // BitsPerSample 8, 0, 8, 0, 8, 0, 8, 0, // BitsPerSample

View File

@ -116,13 +116,48 @@ static tsize_t MyRead(thandle_t opaque, void* dst, tsize_t size) {
return 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, int ReadTIFF(const uint8_t* const data, size_t data_size,
WebPPicture* const pic, int keep_alpha, WebPPicture* const pic, int keep_alpha,
Metadata* const metadata) { Metadata* const metadata) {
MyData my_data = { data, (toff_t)data_size, 0 }; MyData my_data = { data, (toff_t)data_size, 0 };
TIFF* tif; TIFF* tif;
uint32 width, height; uint32_t width, height;
uint32* raster; uint16_t samples_per_px = 0;
uint16_t extra_samples = 0;
uint16_t* extra_samples_ptr = NULL;
uint32_t* raster;
int64_t alloc_size; int64_t alloc_size;
int ok = 0; int ok = 0;
tdir_t dircount; 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", "Only the first will be used, %d will be ignored.\n",
dircount - 1); 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) && if (!(TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width) &&
TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height))) { TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height))) {
fprintf(stderr, "Error! Cannot retrieve TIFF image dimensions.\n"); fprintf(stderr, "Error! Cannot retrieve TIFF image dimensions.\n");
goto End; goto End;
} }
if (!ImgIoUtilCheckSizeArgumentsOverflow((uint64_t)width * height, if (!ImgIoUtilCheckSizeArgumentsOverflow((uint64_t)width * height,
sizeof(*raster))) { sizeof(*raster))) {
goto End; 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. // _Tiffmalloc uses a signed type for size.
alloc_size = (int64_t)((uint64_t)width * height * sizeof(*raster)); alloc_size = (int64_t)((uint64_t)width * height * sizeof(*raster));
if (alloc_size < 0 || alloc_size != (tsize_t)alloc_size) goto End; 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 #ifdef WORDS_BIGENDIAN
TIFFSwabArrayOfLong(raster, width * height); TIFFSwabArrayOfLong(raster, width * height);
#endif #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 ok = keep_alpha
? WebPPictureImportRGBA(pic, (const uint8_t*)raster, stride) ? WebPPictureImportRGBA(pic, (const uint8_t*)raster, stride)
: WebPPictureImportRGBX(pic, (const uint8_t*)raster, stride); : WebPPictureImportRGBX(pic, (const uint8_t*)raster, stride);