From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from ffbox0-bg.ffmpeg.org (ffbox0-bg.ffmpeg.org [79.124.17.100]) by master.gitmailbox.com (Postfix) with ESMTPS id D818844B0D for ; Wed, 19 Nov 2025 05:33:08 +0000 (UTC) Authentication-Results: ffbox; dkim=fail (body hash mismatch (got b'LDOJtsn5FifB9VHVOE/UBrRhOu7UQnRuwvqmKnB4Xfc=', expected b'rrdHqqctRfiwGpBtbV0sgjtzIxmhDMd5U8tW065VhL4=')) header.d=ffmpeg.org header.i=@ffmpeg.org header.a=rsa-sha256 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ffmpeg.org; i=@ffmpeg.org; q=dns/txt; s=mail; t=1763530106; h=mime-version : to : date : message-id : reply-to : subject : list-id : list-archive : list-archive : list-help : list-owner : list-post : list-subscribe : list-unsubscribe : from : cc : content-type : content-transfer-encoding : from; bh=LDOJtsn5FifB9VHVOE/UBrRhOu7UQnRuwvqmKnB4Xfc=; b=zh9Cgq1JwCfiqgWdf8LAylGqEl94LqVBJW0mR7k+KzKaI+ZaljY2pf2TGaVgWEAoEMPV2 K5o5utpOSGHeY2+C7Iq//CKldysDIP29j078Jc+gePMv0MrxKo46JTP2wHjbDugTdS3bzuA RWnGS77lAtx1zkQ0hrh2634kSxIVnfUBzUqp3kYq4LoDp8AKv6qOfmEsFmMNseEoaZSywyo urzbocSqoW4Fx7Xpq+xuKD/xSNwYvOKc8S9QFSpGgvnM004tbmSdPNphuNqKBtBvVrXyoFL U92DNv8FlLx85xjOFzo6/NaSFcTpxx7qlI2cG+4BOyS8BMPxSFFK+hlgxiwQ== Received: from [172.19.0.2] (unknown [172.19.0.2]) by ffbox0-bg.ffmpeg.org (Postfix) with ESMTP id EEEB568FFC9; Wed, 19 Nov 2025 07:28:25 +0200 (EET) ARC-Seal: i=1; cv=none; a=rsa-sha256; d=ffmpeg.org; s=arc; t=1763530056; b=htIuy8C0kgMOmOiVNRoBaXx4x5VA+qnuW6mj49u16URFxHXKWPhJmzA+RFVhfHiabAirB CedFRxCTydJG+2Kmgqxqsj0x1q7Pjd+nlhcKllcCEIDeFd/Jgj7FyqVa5RGooUDFRr8faqi x/ciahCq7XF79QvzCZUL/ALaK0e8c6AdjecvhD3QE4O6UF1wA04DlHr0XJvAAYxq5bzhV0X ijuJfaoZ4VMvSf7D0Jgz3PHVbguYpMvLR0bqIpBCnNzSNecCTwBAT1OqwXAeKWXpMiUa+97 Zx7mETler1kEyJuo4WKrYkohwVK9d6hnM5939P9QsBwGFieNgWHVTA8gcBTw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=ffmpeg.org; s=arc; t=1763530056; h=from : sender : reply-to : subject : date : message-id : to : cc : mime-version : content-type : content-transfer-encoding : content-id : content-description : resent-date : resent-from : resent-sender : resent-to : resent-cc : resent-message-id : in-reply-to : references : list-id : list-help : list-unsubscribe : list-subscribe : list-post : list-owner : list-archive; bh=HjkZOvnJ+a1J8NW250ntUiDf8llJ8K060FmvVEHaKlA=; b=TKJaUZ5dlp38idHYWdfUCneB4Zex4EqSVpdXzrCyBzSOarxyjXGqNidVKlwvO7cHaGgV/ Rex0jCuL/nMUe4zH5Zx8PxKNFuLN4V2iyFMCNpg5nq4DN8R5vc1m299czA5UzM/UK35BDgT ET1P02+ZTvWnWhyO91dAlPWmoglBsBBiKwhiTkMsVB5utya7wQod308thqnAQxBA/F21x8O /q6J3qLFByjVD5RGjKYLjpVveMqY2uUWVr9BWw+aNtXRdC7fBqvFLUtuFrubTOljbhJHqOF /vRNQBgr2WEhahAu73G7GjkmQvnG1WsMh+w/JyRxLIx1AcZofXocfa+YCarQ== ARC-Authentication-Results: i=1; ffmpeg.org; dkim=pass header.d=ffmpeg.org header.i=@ffmpeg.org; arc=none; dmarc=pass header.from=ffmpeg.org policy.dmarc=quarantine Authentication-Results: ffmpeg.org; dkim=pass header.d=ffmpeg.org header.i=@ffmpeg.org; arc=none (Message is not ARC signed); dmarc=pass (Used From Domain Record) header.from=ffmpeg.org policy.dmarc=quarantine DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ffmpeg.org; i=@ffmpeg.org; q=dns/txt; s=mail; t=1763530019; h=content-type : mime-version : content-transfer-encoding : from : to : reply-to : subject : date : from; bh=rrdHqqctRfiwGpBtbV0sgjtzIxmhDMd5U8tW065VhL4=; b=nV8IC8Rug5dmGiKnIerJRvpHOKhFtyeykqIZshv6xy+ReRSM19Ty3iLT4NQSKmS9GAb9V DK1UlrlkCCs7cR6U9TqKndbn2rCdFV5FPiwRieedIztviM+FEB+8FhMVga/aIEMa/+FI5Gl Yc4Tv6A2A68y4dNrgG+Bu/nhzjo3HztMXhh7sbcZ84qgDkLY41K/F6LdkkMTSCNj1X9eYzd +wPYa7DCq9iFx60MPi/tLR200xpmOy7NBRLKNISI2L+xgEKqJriD2lebs83cMCULdPZ0J1W QHe868rOiEEYKj52Q3lRy2Q+Pu8147IfOWwVR4D4VPvbaYsUp5tm9OWmsRHA== Received: from 188d6d40ca7a (code.ffmpeg.org [188.245.149.3]) by ffbox0-bg.ffmpeg.org (Postfix) with ESMTPS id 4DDDC68FFA0 for ; Wed, 19 Nov 2025 07:26:59 +0200 (EET) MIME-Version: 1.0 To: ffmpeg-devel@ffmpeg.org Date: Wed, 19 Nov 2025 05:26:33 -0000 Message-ID: <176353001966.59.13530890815292474692@2cb04c0e5124> Message-ID-Hash: PQ37LHCBZBWQDAN2XXPGKALP7CEWUBG7 X-Message-ID-Hash: PQ37LHCBZBWQDAN2XXPGKALP7CEWUBG7 X-MailFrom: code@ffmpeg.org X-Mailman-Rule-Hits: nonmember-moderation X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; header-match-ffmpeg-devel.ffmpeg.org-0; header-match-ffmpeg-devel.ffmpeg.org-1; header-match-ffmpeg-devel.ffmpeg.org-2; header-match-ffmpeg-devel.ffmpeg.org-3; emergency; member-moderation X-Mailman-Version: 3.3.10 Precedence: list Reply-To: FFmpeg development discussions and patches Subject: [FFmpeg-devel] [PATCH] avcodec/psd: Support auxiliary channels (PR #20966) List-Id: FFmpeg development discussions and patches Archived-At: Archived-At: List-Archive: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: jchw via ffmpeg-devel Cc: jchw Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Archived-At: List-Archive: List-Post: PR #20966 opened by jchw URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/20966 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/20966.patch Photoshop documents can contain additional "auxiliary" channels that don't take part in layer compositing, therefore, using the channel count to determine the presence of an alpha channel in the merged image is incorrect. Instead, as per the PSD specification, use the sign of the layer count (present in the layers and masks section) to determine if there is an alpha channel, then determine the number of "primary" channels using both the presence of the alpha channel and the pixel format. (And yes, this really is the correct way to handle this; it's [documented here](https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_pgfId-1031423) and I manually verified this behavior going back to at least Photoshop 4.0.1.) I've attached a new FATE sample (`psd/lena-rgbxx.psd`) that tests this, an rgb image (no alpha channel) with two extra channels. I'm new to contributing here and it seems like the contributing guide is a bit out of date so please bear with me, I wasn't sure if I should've emailed the FATE sample ahead of time or not. >>From 8e7d560c61910abc2ce6a4a1c9aad1302ddd94c9 Mon Sep 17 00:00:00 2001 From: John Chadwick Date: Tue, 18 Nov 2025 23:45:34 -0500 Subject: [PATCH 1/3] avcodec/psd: Support auxiliary channels Photoshop documents can contain additional "auxiliary" channels that don't take part in layer compositing, therefore, using the channel count to determine the presence of an alpha channel in the merged image is incorrect. Instead, as per the PSD specification, use the sign of the layer count (present in the layers and masks section) to determine if there is an alpha channel, then determine the number of primary channels using both the presence of the alpha channel and the pixel format. --- libavcodec/psd.c | 120 ++++++++++++++++++++++++++++------------------- 1 file changed, 73 insertions(+), 47 deletions(-) diff --git a/libavcodec/psd.c b/libavcodec/psd.c index ea88f254bf..086a926698 100644 --- a/libavcodec/psd.c +++ b/libavcodec/psd.c @@ -52,6 +52,7 @@ typedef struct PSDContext { uint16_t channel_count; uint16_t channel_depth; + uint16_t primary_channels; uint64_t uncompressed_size; unsigned int pixel_size;/* 1 for 8 bits, 2 for 16 bits */ @@ -60,6 +61,8 @@ typedef struct PSDContext { int width; int height; + short layer_count; + enum PsdCompr compression; enum PsdColorMode color_mode; @@ -193,6 +196,13 @@ static int decode_header(PSDContext * s) return AVERROR_INVALIDDATA; } + if (len_section >= 6) { + /* layer count (in layers and masks section) */ + bytestream2_skip(&s->gb, 4); + s->layer_count = bytestream2_get_be16(&s->gb); + len_section -= 6; + } + if (bytestream2_get_bytes_left(&s->gb) < len_section) { av_log(s->avctx, AV_LOG_ERROR, "Incomplete file.\n"); return AVERROR_INVALIDDATA; @@ -301,11 +311,13 @@ static int decode_frame(AVCodecContext *avctx, AVFrame *picture, uint8_t plane_number; PSDContext *s = avctx->priv_data; - s->avctx = avctx; - s->channel_count = 0; - s->channel_depth = 0; - s->tmp = NULL; - s->line_size = 0; + s->avctx = avctx; + s->channel_count = 0; + s->channel_depth = 0; + s->primary_channels = 0; + s->tmp = NULL; + s->line_size = 0; + s->layer_count = 0; bytestream2_init(&s->gb, avpkt->data, avpkt->size); @@ -317,35 +329,28 @@ static int decode_frame(AVCodecContext *avctx, AVFrame *picture, switch (s->color_mode) { case PSD_BITMAP: - if (s->channel_depth != 1 || s->channel_count != 1) { + if (s->channel_depth != 1 || s->channel_count < 1) { av_log(s->avctx, AV_LOG_ERROR, "Invalid bitmap file (channel_depth %d, channel_count %d)\n", s->channel_depth, s->channel_count); return AVERROR_INVALIDDATA; } s->line_size = s->width + 7 >> 3; + s->primary_channels = 1; avctx->pix_fmt = AV_PIX_FMT_MONOWHITE; break; case PSD_INDEXED: - if (s->channel_depth != 8 || s->channel_count != 1) { + if (s->channel_depth != 8 || s->channel_count < 1) { av_log(s->avctx, AV_LOG_ERROR, "Invalid indexed file (channel_depth %d, channel_count %d)\n", s->channel_depth, s->channel_count); return AVERROR_INVALIDDATA; } + s->primary_channels = 1; avctx->pix_fmt = AV_PIX_FMT_PAL8; break; case PSD_CMYK: - if (s->channel_count == 4) { - if (s->channel_depth == 8) { - avctx->pix_fmt = AV_PIX_FMT_GBRP; - } else if (s->channel_depth == 16) { - avctx->pix_fmt = AV_PIX_FMT_GBRP16BE; - } else { - avpriv_report_missing_feature(avctx, "channel depth %d for cmyk", s->channel_depth); - return AVERROR_PATCHWELCOME; - } - } else if (s->channel_count == 5) { + if (s->layer_count < 0 && s->channel_count >= 5) { if (s->channel_depth == 8) { avctx->pix_fmt = AV_PIX_FMT_GBRAP; } else if (s->channel_depth == 16) { @@ -354,22 +359,26 @@ static int decode_frame(AVCodecContext *avctx, AVFrame *picture, avpriv_report_missing_feature(avctx, "channel depth %d for cmyk", s->channel_depth); return AVERROR_PATCHWELCOME; } + s->primary_channels = 5; + } else if (s->channel_count >= 4) { + if (s->channel_depth == 8) { + avctx->pix_fmt = AV_PIX_FMT_GBRP; + } else if (s->channel_depth == 16) { + avctx->pix_fmt = AV_PIX_FMT_GBRP16BE; + } else { + avpriv_report_missing_feature(avctx, "channel depth %d for cmyk", s->channel_depth); + return AVERROR_PATCHWELCOME; + } + s->primary_channels = 4; } else { - avpriv_report_missing_feature(avctx, "channel count %d for cmyk", s->channel_count); - return AVERROR_PATCHWELCOME; + av_log(s->avctx, AV_LOG_ERROR, + "Invalid cmyk file (channel_count %d)\n", + s->channel_count); + return AVERROR_INVALIDDATA; } break; case PSD_RGB: - if (s->channel_count == 3) { - if (s->channel_depth == 8) { - avctx->pix_fmt = AV_PIX_FMT_GBRP; - } else if (s->channel_depth == 16) { - avctx->pix_fmt = AV_PIX_FMT_GBRP16BE; - } else { - avpriv_report_missing_feature(avctx, "channel depth %d for rgb", s->channel_depth); - return AVERROR_PATCHWELCOME; - } - } else if (s->channel_count == 4) { + if (s->layer_count < 0 && s->channel_count >= 4) { if (s->channel_depth == 8) { avctx->pix_fmt = AV_PIX_FMT_GBRAP; } else if (s->channel_depth == 16) { @@ -378,15 +387,38 @@ static int decode_frame(AVCodecContext *avctx, AVFrame *picture, avpriv_report_missing_feature(avctx, "channel depth %d for rgb", s->channel_depth); return AVERROR_PATCHWELCOME; } + s->primary_channels = 4; + } else if (s->channel_count >= 3) { + if (s->channel_depth == 8) { + avctx->pix_fmt = AV_PIX_FMT_GBRP; + } else if (s->channel_depth == 16) { + avctx->pix_fmt = AV_PIX_FMT_GBRP16BE; + } else { + avpriv_report_missing_feature(avctx, "channel depth %d for rgb", s->channel_depth); + return AVERROR_PATCHWELCOME; + } + s->primary_channels = 3; } else { - avpriv_report_missing_feature(avctx, "channel count %d for rgb", s->channel_count); - return AVERROR_PATCHWELCOME; + av_log(s->avctx, AV_LOG_ERROR, + "Invalid rgb file (channel_count %d)\n", + s->channel_count); + return AVERROR_INVALIDDATA; } break; case PSD_DUOTONE: av_log(avctx, AV_LOG_WARNING, "ignoring unknown duotone specification.\n"); case PSD_GRAYSCALE: - if (s->channel_count == 1) { + if (s->layer_count < 0 && s->channel_count >= 2) { + if (s->channel_depth == 8) { + avctx->pix_fmt = AV_PIX_FMT_YA8; + } else if (s->channel_depth == 16) { + avctx->pix_fmt = AV_PIX_FMT_YA16BE; + } else { + avpriv_report_missing_feature(avctx, "channel depth %d for grayscale", s->channel_depth); + return AVERROR_PATCHWELCOME; + } + s->primary_channels = 2; + } else if (s->channel_count >= 1) { if (s->channel_depth == 8) { avctx->pix_fmt = AV_PIX_FMT_GRAY8; } else if (s->channel_depth == 16) { @@ -397,18 +429,12 @@ static int decode_frame(AVCodecContext *avctx, AVFrame *picture, avpriv_report_missing_feature(avctx, "channel depth %d for grayscale", s->channel_depth); return AVERROR_PATCHWELCOME; } - } else if (s->channel_count == 2) { - if (s->channel_depth == 8) { - avctx->pix_fmt = AV_PIX_FMT_YA8; - } else if (s->channel_depth == 16) { - avctx->pix_fmt = AV_PIX_FMT_YA16BE; - } else { - avpriv_report_missing_feature(avctx, "channel depth %d for grayscale", s->channel_depth); - return AVERROR_PATCHWELCOME; - } + s->primary_channels = 1; } else { - avpriv_report_missing_feature(avctx, "channel count %d for grayscale", s->channel_count); - return AVERROR_PATCHWELCOME; + av_log(s->avctx, AV_LOG_ERROR, + "Invalid grayscale file (channel_count %d)\n", + s->channel_count); + return AVERROR_INVALIDDATA; } break; default: @@ -446,10 +472,10 @@ static int decode_frame(AVCodecContext *avctx, AVFrame *picture, /* Store data */ if ((avctx->pix_fmt == AV_PIX_FMT_YA8)||(avctx->pix_fmt == AV_PIX_FMT_YA16BE)){/* Interleaved */ ptr = picture->data[0]; - for (c = 0; c < s->channel_count; c++) { + for (c = 0; c < 2; c++) { for (y = 0; y < s->height; y++) { for (x = 0; x < s->width; x++) { - index_out = y * picture->linesize[0] + x * s->channel_count * s->pixel_size + c * s->pixel_size; + index_out = y * picture->linesize[0] + x * 2 * s->pixel_size + c * s->pixel_size; for (p = 0; p < s->pixel_size; p++) { ptr[index_out + p] = *ptr_data; ptr_data ++; @@ -518,10 +544,10 @@ static int decode_frame(AVCodecContext *avctx, AVFrame *picture, } } } else {/* Planar */ - if (s->channel_count == 1)/* gray 8 or gray 16be */ + if (s->primary_channels == 1)/* bitmap, indexed, grayscale */ eq_channel[0] = 0;/* assign first channel, to first plane */ - for (c = 0; c < s->channel_count; c++) { + for (c = 0; c < s->primary_channels; c++) { plane_number = eq_channel[c]; ptr = picture->data[plane_number];/* get the right plane */ for (y = 0; y < s->height; y++) { -- 2.49.1 >>From 306907257009904d64e7bddd51d1eac39e7ead6d Mon Sep 17 00:00:00 2001 From: John Chadwick Date: Wed, 19 Nov 2025 00:17:21 -0500 Subject: [PATCH 2/3] tests/ref/fate: Add psd-rgbxx This is an rgb test image with two auxilliary channels and no alpha channels. --- tests/ref/fate/psd-rgbxx | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 tests/ref/fate/psd-rgbxx diff --git a/tests/ref/fate/psd-rgbxx b/tests/ref/fate/psd-rgbxx new file mode 100644 index 0000000000..7f8f550afe --- /dev/null +++ b/tests/ref/fate/psd-rgbxx @@ -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, 49152, 0xe0013dee -- 2.49.1 >>From a075417d629858aca2ab07b92d6b1bdb3a43bfc4 Mon Sep 17 00:00:00 2001 From: John Chadwick Date: Wed, 19 Nov 2025 00:17:34 -0500 Subject: [PATCH 3/3] tests/fate/image: Add psd-rgbxx --- tests/fate/image.mak | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/fate/image.mak b/tests/fate/image.mak index 3facd8aaa8..be4b9623ad 100644 --- a/tests/fate/image.mak +++ b/tests/fate/image.mak @@ -444,7 +444,7 @@ FATE_PSD-$(call DEMDEC, IMAGE2, PSD, SCALE_FILTER) += fate-psd-$(1) fate-psd-$(1): CMD = framecrc -i $(TARGET_SAMPLES)/psd/lena-$(1).psd -sws_flags +accurate_rnd+bitexact -pix_fmt rgb24 -vf scale endef -PSD_COLORSPACES = gray8 gray16 rgb24 rgb48 rgba rgba64 ya8 ya16 +PSD_COLORSPACES = gray8 gray16 rgb24 rgb48 rgba rgbxx rgba64 ya8 ya16 $(foreach CLSP,$(PSD_COLORSPACES),$(eval $(call FATE_IMGSUITE_PSD,$(CLSP)))) FATE_PSD += fate-psd-lena-127x127-rgb24 -- 2.49.1 _______________________________________________ ffmpeg-devel mailing list -- ffmpeg-devel@ffmpeg.org To unsubscribe send an email to ffmpeg-devel-leave@ffmpeg.org