PR #24443 opened by Forgejo_Fairy URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24443 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24443.patch
PSD merged previews contain color channels composited against white. Copying them directly into straight-alpha frames produces washed-out colors and white fringes. In the sample from #23596, pixel (50, 50) currently decodes as RGBA (191, 128, 128, 127); this change produces the expected (126, 0, 0, 127). Remove the white contribution from supported 8-bit and 16-bit alpha output, including grayscale and RGB converted from CMYK. Preserve alpha, clip negative reconstructed colors, and use zero color for fully transparent pixels. Unsigned multiplication accommodates the full 16-bit product. Existing alpha detection continues to distinguish merged transparency from auxiliary channels. Update three affected FATE references and add a native grayscale-alpha test using the existing lena-ya8.psd sample. No new FATE samples are required. Validation on Linux aarch64: - The supplied gradient matches the corrected attachment pixel for pixel. - All 17 PSD FATE tests pass with `make fate-psd SAMPLES=/opt/fate-suite`. - All 48 generated fixtures pass across 8/16-bit RGB, grayscale and CMYK, RAW/RLE, transparency present/absent, and auxiliary channels present/absent. - Corrected RGB FATE samples match psd-tools 1.19.0 for all nonzero-alpha pixels, and alpha matches everywhere. The standalone validator and investigation record are preserved on `fairy/issue23596-tooling` under `tools/issue23596/`. Reconstruction remains subject to quantization already present in the stored composite. Fixes #23596. >From b713d73057497cf200cbae8519bf5a3a5abd0398 Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Thu, 10 Sep 2026 18:26:21 +0000 Subject: [PATCH] avcodec/psd: remove white matte from merged transparency PSD merged previews blend color channels against white. Copying those channels into a straight-alpha frame causes washed-out colors and white fringes in partially transparent areas. Undo the white composite for 8- and 16-bit alpha output, including grayscale and RGB converted from CMYK. Preserve alpha, clip negative reconstructed colors and use zero color for fully transparent pixels. Use unsigned multiplication to accommodate the full 16-bit product without overflow. Update the affected FATE references and add native grayscale-alpha output coverage using the existing sample. No new FATE samples are needed. The supplied gradient now matches the issue's corrected PNG pixel for pixel. All 17 PSD FATE tests and 48 generated RAW/RLE fixtures pass. Fixes: #23596 Assisted-by: Fairy --- libavcodec/psd.c | 37 +++++++++++++++++++++ tests/fate/image.mak | 3 ++ tests/ref/fate/psd-lena-rgba-rle-128x128-8b | 2 +- tests/ref/fate/psd-rgba | 2 +- tests/ref/fate/psd-ya8 | 2 +- tests/ref/fate/psd-ya8-alpha | 6 ++++ 6 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 tests/ref/fate/psd-ya8-alpha diff --git a/libavcodec/psd.c b/libavcodec/psd.c index 9dc56f8f5e..684e26feb6 100644 --- a/libavcodec/psd.c +++ b/libavcodec/psd.c @@ -300,6 +300,39 @@ static int decode_rle(PSDContext * s){ return 0; } +/* The merged image is composited against white, rather than stored with + * straight alpha. The product fits in unsigned int for both supported depths. */ +static unsigned unmatte(int color, int alpha, int max) +{ + return alpha ? FFMAX(color + alpha - max, 0) * (unsigned)max / alpha : 0; +} + +static void remove_white_matte(PSDContext *s, AVFrame *frame) +{ + int gray = s->color_mode == PSD_GRAYSCALE || s->color_mode == PSD_DUOTONE; + int step = s->pixel_size * (gray ? 2 : 1); + + for (int c = 0; c < (gray ? 1 : 3); c++) { + uint8_t *color = frame->data[c]; + const uint8_t *alpha = gray ? frame->data[0] + s->pixel_size : frame->data[3]; + + for (int y = 0; y < s->height; y++) { + if (s->channel_depth == 8) { + for (int x = 0; x < s->width; x++) + color[x * step] = unmatte(color[x * step], alpha[x * step], 255); + } else { + for (int x = 0; x < s->width; x++) { + unsigned v = unmatte(AV_RB16(color + x * step), + AV_RB16(alpha + x * step), 65535); + AV_WB16(color + x * step, v); + } + } + color += frame->linesize[c]; + alpha += frame->linesize[gray ? 0 : 3]; + } + } +} + static int decode_frame(AVCodecContext *avctx, AVFrame *picture, int *got_frame, AVPacket *avpkt) { @@ -563,6 +596,10 @@ static int decode_frame(AVCodecContext *avctx, AVFrame *picture, memcpy(picture->data[1], s->palette, AVPALETTE_SIZE); } + if (avctx->pix_fmt == AV_PIX_FMT_GBRAP || avctx->pix_fmt == AV_PIX_FMT_GBRAP16BE || + avctx->pix_fmt == AV_PIX_FMT_YA8 || avctx->pix_fmt == AV_PIX_FMT_YA16BE) + remove_white_matte(s, picture); + av_freep(&s->tmp); picture->pict_type = AV_PICTURE_TYPE_I; diff --git a/tests/fate/image.mak b/tests/fate/image.mak index a20fe183dc..4f52439c83 100644 --- a/tests/fate/image.mak +++ b/tests/fate/image.mak @@ -465,6 +465,9 @@ fate-psd-lena-rgb-rle-127x127-8b: CMD = framecrc -i $(TARGET_SAMPLES)/psd/lena-r FATE_PSD += fate-psd-lena-rgba-rle-128x128-8b fate-psd-lena-rgba-rle-128x128-8b: CMD = framecrc -i $(TARGET_SAMPLES)/psd/lena-rgba_rle_128x128_8b.psd +FATE_PSD += fate-psd-ya8-alpha +fate-psd-ya8-alpha: CMD = framecrc -i $(TARGET_SAMPLES)/psd/lena-ya8.psd + FATE_PSD += fate-psd-lena-256c fate-psd-lena-256c: CMD = framecrc -i $(TARGET_SAMPLES)/psd/lena-256c.psd diff --git a/tests/ref/fate/psd-lena-rgba-rle-128x128-8b b/tests/ref/fate/psd-lena-rgba-rle-128x128-8b index f3a11b7c5c..d63c80e5e2 100644 --- a/tests/ref/fate/psd-lena-rgba-rle-128x128-8b +++ b/tests/ref/fate/psd-lena-rgba-rle-128x128-8b @@ -3,4 +3,4 @@ #codec_id 0: rawvideo #dimensions 0: 128x128 #sar 0: 0/1 -0, 0, 0, 1, 65536, 0xaaacdff3 +0, 0, 0, 1, 65536, 0x5c2127b2 diff --git a/tests/ref/fate/psd-rgba b/tests/ref/fate/psd-rgba index 672898093d..4335c14eb7 100644 --- a/tests/ref/fate/psd-rgba +++ b/tests/ref/fate/psd-rgba @@ -3,4 +3,4 @@ #codec_id 0: rawvideo #dimensions 0: 128x128 #sar 0: 0/1 -0, 0, 0, 1, 49152, 0xb2563b4a +0, 0, 0, 1, 49152, 0x72620a93 diff --git a/tests/ref/fate/psd-ya8 b/tests/ref/fate/psd-ya8 index 9a59fbf5a8..6d143c42dc 100644 --- a/tests/ref/fate/psd-ya8 +++ b/tests/ref/fate/psd-ya8 @@ -3,4 +3,4 @@ #codec_id 0: rawvideo #dimensions 0: 128x128 #sar 0: 0/1 -0, 0, 0, 1, 49152, 0xfa0ee1d0 +0, 0, 0, 1, 49152, 0xe0da1ccd diff --git a/tests/ref/fate/psd-ya8-alpha b/tests/ref/fate/psd-ya8-alpha new file mode 100644 index 0000000000..619c52ded8 --- /dev/null +++ b/tests/ref/fate/psd-ya8-alpha @@ -0,0 +1,6 @@ +#tb 0: 1/25 +#media_type 0: video +#codec_id 0: rawvideo +#dimensions 0: 128x128 +#sar 0: 0/1 +0, 0, 0, 1, 32768, 0xb9ca81ce -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
