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 616484FD40 for ; Tue, 1 Jul 2025 06:24:19 +0000 (UTC) Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.ffmpeg.org (Postfix) with ESMTP id 11A7E68EB9E; Tue, 1 Jul 2025 09:24:15 +0300 (EEST) To: "ffmpeg-devel@ffmpeg.org" Date: Tue, 1 Jul 2025 06:24:01 +0000 MIME-Version: 1.0 Message-ID: List-Id: FFmpeg development discussions and patches List-Post: From: Sarthak Indurkhya via ffmpeg-devel Precedence: list Cc: Sarthak Indurkhya X-Mailman-Version: 2.1.29 X-BeenThere: ffmpeg-devel@ffmpeg.org List-Subscribe: , List-Unsubscribe: , List-Archive: Reply-To: FFmpeg development discussions and patches List-Help: Subject: [FFmpeg-devel] [PATCH] avfilter: add inverse tone mapping filter Content-Type: multipart/mixed; boundary="===============8269019266584589001==" Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" Archived-At: List-Archive: List-Post: --===============8269019266584589001== Content-Type: message/rfc822 Content-Disposition: inline Return-Path: X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from NAM04-BN8-obe.outbound.protection.outlook.com (mail-bn8nam04on2051.outbound.protection.outlook.com [40.107.100.51]) by ffbox0-bg.ffmpeg.org (Postfix) with ESMTPS id 778D368EB9A for ; Tue, 1 Jul 2025 09:24:08 +0300 (EEST) ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=be1YbIX0d2GCQn0xI+jt2UT7jjDlmi2rXgcUVHQMPwsV0PLZvkda7Bx2IVLpORgcFu2cwfvKIZFq22PUvfYaUenxQjK2dP7JtNgB57Kau4YdQSXTvM+hc9bODgzilJc7UVT7s8Qcmr9FEY8mtulC4MT8qssd5/H5NqFy0lc7m6gz+a2FO9oWNvkChEkhQ/Mc+AXtjtzuKsjbhYAB7hILc+DCx5ZYoSI26pVgd6hzqSzasPoXie7g1XGiqa5aB8X+XZcZEHlnFW/URwT5f9cP1yRw9QB8D/YbGFG6/S7Qni295d6sXdSUaox+pEVYc2yIohTC2jup48ey9tuXpyg8UQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector10001; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=LjClXFo5jId3kZ1zA67Pv9FOPy2F+4l6ToKtirv+wXU=; b=I9bidLFnTcOBUD5mD5JojyrJ8L+zaDXIm+zVNr5aH0wW06xJJcuaMUW8Bg5OiBs2E4q8pOsJkVu0xHtb/69leaQ7KJAPP3Uo7bpUb/fedXunbafsaXfd95DyRQiMruuP3MFfuTEuBv5875PCMjsQtVPIZTK4at3ZjPXi9FI2MJ0H645gCmSb267MRMMJiNohBDpYDLDvH4EtxZ+YawfQv+ytRoY6kPlSJtehjyQknEOJ0kBgohnuZSiAKRsHverMXyl4ZQ7XJT+IRH3lnWKYjl6gF7UJti8KJOyE4aKDhrb2Lv7wjwqDwCM+SO2uubMVZPFWsCeCakx9ZLQ5hWKyKQ== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass smtp.mailfrom=adobe.com; dmarc=pass action=none header.from=adobe.com; dkim=pass header.d=adobe.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=adobe.com; s=selector2; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=LjClXFo5jId3kZ1zA67Pv9FOPy2F+4l6ToKtirv+wXU=; b=J7cy6dEZISFkH1U0SINz+gaJgxX9fdK6r5Fb7YOJZ0qhivh9/2vkipGeM2W0KL4jTiTl9wfggXDqG+z90zOC0tShky0rlZINFk5ipBqkuNqMbSyMCP3x0PTfUzn6J1HXbkgE2VOGaJOGVhh6VJKLhWwAwt5pZmlDcU65L3BIp90= Received: from BN0PR02MB7887.namprd02.prod.outlook.com (2603:10b6:408:14b::16) by PH7PR02MB9292.namprd02.prod.outlook.com (2603:10b6:510:275::9) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.8857.28; Tue, 1 Jul 2025 06:24:03 +0000 Received: from BN0PR02MB7887.namprd02.prod.outlook.com ([fe80::5019:a130:f524:7826]) by BN0PR02MB7887.namprd02.prod.outlook.com ([fe80::5019:a130:f524:7826%3]) with mapi id 15.20.8901.018; Tue, 1 Jul 2025 06:24:01 +0000 From: Sarthak Indurkhya To: "ffmpeg-devel@ffmpeg.org" Subject: [PATCH] avfilter: add inverse tone mapping filter Thread-Topic: [PATCH] avfilter: add inverse tone mapping filter Thread-Index: AQHb6k0c2lrsVIsxq0ufFDu3PwqJGQ== Date: Tue, 1 Jul 2025 06:24:01 +0000 Message-ID: Accept-Language: en-IN, en-US Content-Language: en-IN X-MS-Has-Attach: X-MS-TNEF-Correlator: x-ms-reactions: allow authentication-results: dkim=none (message not signed) header.d=none;dmarc=none action=none header.from=adobe.com; x-ms-publictraffictype: Email x-ms-traffictypediagnostic: BN0PR02MB7887:EE_|PH7PR02MB9292:EE_ x-ms-office365-filtering-correlation-id: dfc5c554-6832-4b66-247d-08ddb867def7 x-ms-exchange-senderadcheck: 1 x-ms-exchange-antispam-relay: 0 x-microsoft-antispam: BCL:0;ARA:13230040|1800799024|366016|376014|8096899003|38070700018; x-microsoft-antispam-message-info: =?us-ascii?Q?KdhJ/G6Zoz6y6xm0w9fxlAN7Ik96cejiJXX2KcUiJBVVGLLUXKeBBqaL83hu?= =?us-ascii?Q?fcGzTVGKx/xf8BQ+6WNBjNk2N5SA+PK/G9RTDLl08gL3+ajzVCVDZjRYZLqw?= =?us-ascii?Q?X/h+RjtGUXNYgjx9Dyb5hIkNkx0qJxLbxfaT7cYsscEpjZ8K/BNDe5xzeSxC?= =?us-ascii?Q?GY+w7Lc1aEqJ4oLarZ2DwWI8Q06jGFOEoWg3ERItEt7MQXupkveiZThKd9uA?= =?us-ascii?Q?eV3bc6Kx451koXlWO7bnDZVou4Va3Vv0msICv+ACR5rvVqMQToYfMmLzxecm?= =?us-ascii?Q?cxkB6uPYz3qIxSHW1tXaoUCMRTktZCtm8ZC3rmw1jCoUOKCR4IYuvH1ufztF?= =?us-ascii?Q?bdifTDl9L3zycfJendljUgIGfiFzznwb4eEALEbAGnKRvZi+GUbmvy6vgNtv?= =?us-ascii?Q?l2TRkLFd8zoVqjjGZTsBEINYLM0NUwW7XTax+tv0eukShdsKcl79+fvOXYmg?= =?us-ascii?Q?UzkmNx2cfCkxPEOLvQJB1U81q1I0qTQbW/wxWkP8+79Den4lD9mwUdGOeSwc?= =?us-ascii?Q?KY2PWca9u0NCm77Trq8bHGQ8boBjpQ6ztzRJc8AMqMnYhsNwtu6isRIbuMJc?= =?us-ascii?Q?PNOya3rYNr+ujucauMuW7DE3kVzU5EfrgS8+vzc/zZcPnMjaaDzj6M8bJBwL?= =?us-ascii?Q?Pgod/rbPqlwEaH8UdAcBm9hJlTozqS1kaRyLRB+q9+u9G4fMMRjNFtKGL/m0?= =?us-ascii?Q?uimzHOHERhb5DZCfZqcapkpyQNlhWlZJkI++CD/kwFLbb0tg7u1Qc4lHTI73?= =?us-ascii?Q?EQZsBHlspoCA1MszQL2E9NhRkd3Dd+9t5qpprNwgMBj7vQrEd+inVypfzKSc?= =?us-ascii?Q?hVNmRHY7eZkDn2oEeYE4+LxYhaSiR30RLpalLe1t0pObGtgmS+cXYV+5yT1j?= =?us-ascii?Q?qiaIw2t+g8I/F+kZKdKJIykeNucIpQd3q9oSXvU2uYkK1KNv+KQ9U+TBNEQx?= =?us-ascii?Q?JKRGfuT6nBYcb4r5xLcUgjYMRZrpnLoc692fT+3Fef/jTLz/voTgoiO5TDxu?= =?us-ascii?Q?NfCI+nq8DXS++Ya/2Up9bOG9N4pY7A8aXrHyvMQpqGaO4lmML65/kTTDa8Ia?= =?us-ascii?Q?jApTf+g7iD7glhkcTOOPsFegYgspQjrG2g6lkvKGzsHYhxjdjvo8XAiw/IXZ?= =?us-ascii?Q?dvuNfOAuLGU0olwt2lWo8D16IGNtSZxXPwxrMOPbp1jlie5JumSElJ6QsuG4?= =?us-ascii?Q?4G5mjeuybKQux+E4BYTFLaFNdJtciiMDsYfI4nTJvJvmGZOkHM6/e24/wt7S?= =?us-ascii?Q?4nNM71a4HeElvPRBPxjtcb5Ed7/jDIsqx0xHSxhrfNxRccc74/Gdy7WBDYmJ?= =?us-ascii?Q?F8Nhnrjmo6Xp6Mn1plVtq/dyb0ec8RZetUiMZ56UieikN/88CG0QVikj7W3E?= =?us-ascii?Q?3EmdQTqgTrSHRMkAST+0pxPcNZ7S1X+q6InAJXnfmMbioT5Jyn0c+SdxMB07?= =?us-ascii?Q?fBdFjjSWnwo=3D?= x-forefront-antispam-report: CIP:255.255.255.255;CTRY:;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:BN0PR02MB7887.namprd02.prod.outlook.com;PTR:;CAT:NONE;SFS:(13230040)(1800799024)(366016)(376014)(8096899003)(38070700018);DIR:OUT;SFP:1101; x-ms-exchange-antispam-messagedata-chunkcount: 1 x-ms-exchange-antispam-messagedata-0: =?us-ascii?Q?P/220yZ/lnCdvEdCoXCjmxsQ3CYHVEQDxOxPqqLWysO6OAOtglBVEUrJUx2G?= =?us-ascii?Q?zY85j+hXaYpXG2L58dmbYP1c6//2xTbpYdiImLIlpZh0epwalgmdIcUivbTp?= =?us-ascii?Q?xRFV8CiF5uVLc3P9c2AAgyrzqcIwz1675UIsg9bx7dnD4mlMBOkh9EHBs5/x?= =?us-ascii?Q?wYzfdNAo2Pi7TeMjOzrV1fqrOpj8RIIv0P52lbFdUrU/XHzbN3rqEiV8vEJ4?= =?us-ascii?Q?dlw6CYyxqIrborm5KeaDwounJ7Ys6HgK16wneRfussk51Nt331iYxSU5CQUk?= =?us-ascii?Q?XY/grJhOHqT8DS+0jwgQHFvZk4WozGpc8OGtRcmlhXH2xI8qNViuLV55COwu?= =?us-ascii?Q?qijLmknusfNadxHN4zLoMCMNpifwLhad5x5LJSAY3dhio9yzSySkeAbKfkg/?= =?us-ascii?Q?T5e2Eyd1rdV9p7dPekJbOj/qyUbhnNAhlnW3cCwHYa7O3WTxPksbg5y6cK87?= =?us-ascii?Q?1d6uhvwpnz75SElke4iO27GH3UUb0hyo1/mEa0Qxndt0ru+U/67fgclR5E/3?= =?us-ascii?Q?vq3RjMuC1remSASCUsfM8WVkpzGxnq4UgbeMFXWoQ8RslUD2lIf0Xqy3znZ8?= =?us-ascii?Q?2f/Sver0DwliK3YvZ4mtF/aZoDEPdXto+cUl+dMKSQSSrGyCBnzugp8L24t4?= =?us-ascii?Q?xNa59Ee/7Kz32Z2NO677RMT7rhTNv4Z7Qm1zFdLv1M3ffqrbH703mkRYUXlj?= =?us-ascii?Q?thJUpobgEKH0iQ92JgW0e2K/CrOIRqIXH+OPXhvcxxnk+Zav30hH7KLip7BN?= =?us-ascii?Q?btw87R386K/kNxQ7gAUdAq6IK5kKuSbGoNM1dL4FYDPcoCZEh96CTpT7Eipy?= =?us-ascii?Q?U3m971laqEoZR7mh5hksB61PvmasTZaXLrSG90cGlZS+4mkYB4lLhK+HOT+v?= =?us-ascii?Q?OkbX1RShtLkSo2r5Fi3M88WZkpPqpU1eebKwFebiMVGEOCifSOjtPH/DsZ3Z?= =?us-ascii?Q?7HWocGpTbqtt6/sTk+L9ET8SApe/SyT8QjTYYxHbv3N2fajXQaSlbSf8omo1?= =?us-ascii?Q?RGV2209B9TiEP0Ruq2EpGQbzpZeppnIVy1tdb1Dbc5rloeRQWDjhJfI2Mwmr?= =?us-ascii?Q?8GsAHXdtk7kTF/kgnzOKG846H9LkB0PiMxW92Bw1BY/KkAsTqNutR3SH2I/s?= =?us-ascii?Q?j70M2v2UUYQ0KGcvJBJMeiv2wJBDYydxUDeEGwpJprRwD00sFLvXWRfsY+Ss?= =?us-ascii?Q?xBZcBNZvFGOZCaBEXeZdRrHYkUsutIp2vCG3WJvTAqXkbcetUPzIp64b8EIA?= =?us-ascii?Q?cxtT3ISbnCMTRXNZFnYF8U2Q8Z7QgsyUXkD+Ztcc1anxj3pZrYYvtocNBTp0?= =?us-ascii?Q?OUvPqxAKzyK7+sIomVfQMsYc0r15aPH+k5ZxHLe2T5kLJQfob10j73UfKl/W?= =?us-ascii?Q?lPKWnXukF0JhsHPWfHA7Hyv6AwxGupnpGgGEZfEASHhHIjd16UH8iby+nuQb?= =?us-ascii?Q?jXWbCvM7R2yW3wR2uR5AzYfYIdFvXmFAQaxM7cf4dNi4xp2huyZSqdGjwuXJ?= =?us-ascii?Q?JXX7HyoBjSHWGPKWA4rFtBCVgxFQKSf7bv3DDKw/X1z6JZtYQwVf4C5rtBSN?= =?us-ascii?Q?r/1pu6b2pxG4jRQZKZyk0ZEOlbCCyAx/cZYfAaqjKXqPySn0t4PhHrrOgGDa?= =?us-ascii?Q?Pw=3D=3D?= MIME-Version: 1.0 X-OriginatorOrg: adobe.com X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-AuthSource: BN0PR02MB7887.namprd02.prod.outlook.com X-MS-Exchange-CrossTenant-Network-Message-Id: dfc5c554-6832-4b66-247d-08ddb867def7 X-MS-Exchange-CrossTenant-originalarrivaltime: 01 Jul 2025 06:24:01.8318 (UTC) X-MS-Exchange-CrossTenant-fromentityheader: Hosted X-MS-Exchange-CrossTenant-id: fa7b1b5a-7b34-4387-94ae-d2c178decee1 X-MS-Exchange-CrossTenant-mailboxtype: HOSTED X-MS-Exchange-CrossTenant-userprincipalname: SDvZO3aEuyzRIN1nRR2wbX7IPB8vrI2BTqXwFW4lwkwPVnXpYjo3Xc8dGrryNZS9vuKSq4HNq/W+eZPDZfstPQ== X-MS-Exchange-Transport-CrossTenantHeadersStamped: PH7PR02MB9292 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: quoted-printable X-Content-Filtered-By: Mailman/MimeDel 2.1.29 Hello FFmpeg developers, This patch introduces a new video filter called inversetonemap for FFmpeg. The filter performs SDR to HDR conversion by mapping SDR BT.709 video to HD= R BT.2020 PQ, using local adaptation and inverse tone mapping. The goal is = to provide a simple, flexible tool for upconverting SDR content for HDR dis= plays, with local adaptation, tone curve sensitivity, and chroma processing= . Please review. Thanks, Sarthak --- >From fdcd2f2c2b570bb59d1bf7a4de6df1865a0b0023 Mon Sep 17 00:00:00 2001 From: Sarthak Indurkhya sarthak@Sarthaks-MacBook-Pro.local Date: Tue, 1 Jul 2025 11:12:20 +0530 Subject: [PATCH] avfilter: add inversetonemap filter This filter performs inverse tone mapping from SDR to HDR using local adapt= ation and PQ mapping. Usage: ffmpeg -i input.mp4 -vf inversetonemap -c:v libx265 -x265-params range=3D= full -crf 20 -tag:v hvc1 output.mp4 Signed-off-by: Sarthak Indurkhya sarthak@Sarthaks-MacBook-Pro.local --- libavfilter/vf_inversetonemap.c | 503 ++++++++++++++++++++++++++++++++ 1 file changed, 503 insertions(+) create mode 100644 libavfilter/vf_inversetonemap.c diff --git a/libavfilter/vf_inversetonemap.c b/libavfilter/vf_inversetonema= p.c new file mode 100644 index 0000000000..28ea1ef29e --- /dev/null +++ b/libavfilter/vf_inversetonemap.c @@ -0,0 +1,503 @@ +/** + * @file + * @brief SDR to HDR inverse tone mapping filter for FFmpeg + * @author Sarthak Indurkhya + * + * This filter converts SDR BT.709 video to HDR BT.2020 PQ, + * using local adaptation and inverse tone mapping. + */ + + +#include +#include +#include +#include +#include + +#include "libavutil/pixdesc.h" +#include "libavutil/mem.h" +#include "libavutil/opt.h" +#include "libavutil/log.h" +#include "avfilter.h" +#include "libavutil/imgutils.h" +#include "formats.h" +#include "filters.h" +#include "libswscale/swscale.h" +#include "libavutil/frame.h" +#include "libavfilter/video.h" +#include + +#define OFFSET(x) offsetof(FilterContext, x) +#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM + +typedef struct FilterContext { + const AVClass *class; + float sigma_spatial; + float sigma_range; + float n; + float HDR_max; +} FilterContext; + +//declaring lookup table +static float bt709_gamma_lut[256]; + +static void init_bt709_gamma_lut(void) { + for (int i =3D 0; i < 256; i++) { + float v =3D i / 255.0f; + if (v < 0.081f) + bt709_gamma_lut[i] =3D v / 4.5f; + else + bt709_gamma_lut[i] =3D powf((v + 0.099f) / 1.099f, 1.0f / 0.45= f); + } +} + +// Rec.709 to Rec.2020 matrix +static const float bt709_to_bt2020[3][3] =3D { + {0.6274f, 0.3293f, 0.0433f}, + {0.0691f, 0.9195f, 0.0114f}, + {0.0164f, 0.0880f, 0.8956f} +}; + +// PQ transfer function +#define PQ_M1 0.1593017578125f +#define PQ_M2 78.84375f +#define PQ_C1 0.8359375f +#define PQ_C2 18.8515625f +#define PQ_C3 18.6875f + +static float linear_to_pq(float L) { + float Lm; + L /=3D 10000.0f; + Lm =3D powf(L, PQ_M1); + return powf((PQ_C1 + PQ_C2 * Lm) / (1.0f + PQ_C3 * Lm), PQ_M2); +} + +static void compute_local_adaptation(const float *R_full, float *sigma, in= t width, int height, + float sigma_spatial, float sigma_range, float HDR_max) { + + int radius =3D (int)(3 * sigma_spatial); + int npix =3D width * height; + float *temp_blur =3D av_mallocz(npix * sizeof(float)); + float *spatial_weights =3D av_malloc((2 * radius + 1) * sizeof(float))= ; + + // 1D Gaussian kernel for separable blur + for (int i =3D -radius; i <=3D radius; i++) { + spatial_weights[i + radius] =3D expf(-(i * i) / (2 * sigma_spatial= * sigma_spatial)); + } + + // Scene max luminance + float scene_max =3D 1e-6f; + for (int i =3D 0; i < npix; i++) { + if (R_full[i] > scene_max) + scene_max =3D R_full[i]; + } + float hdr_scale =3D HDR_max / scene_max; + + // ---- Horizontal blur pass ---- + for (int y =3D 0; y < height; y++) { + for (int x =3D 0; x < width; x++) { + float sum =3D 0.0f, sum_weights =3D 0.0f; + for (int dx =3D -radius; dx <=3D radius; dx++) { + int nx =3D x + dx; + if (nx < 0 || nx >=3D width) continue; + + float w =3D spatial_weights[dx + radius]; + float intensity_diff =3D R_full[y * width + nx] - R_full[y= * width + x]; + float w_range =3D expf(-(intensity_diff * intensity_diff) = / (2 * sigma_range * sigma_range)); + + float weight =3D w * w_range; + sum +=3D weight * R_full[y * width + nx]; + sum_weights +=3D weight; + } + temp_blur[y * width + x] =3D (sum_weights > 0.0f) ? (sum / sum= _weights) : R_full[y * width + x]; + } + } + + // ---- Vertical blur pass ---- + for (int y =3D 0; y < height; y++) { + for (int x =3D 0; x < width; x++) { + float sum =3D 0.0f, sum_weights =3D 0.0f; + for (int dy =3D -radius; dy <=3D radius; dy++) { + int ny =3D y + dy; + if (ny < 0 || ny >=3D height) continue; + + float w =3D spatial_weights[dy + radius]; + float intensity_diff =3D temp_blur[ny * width + x] - temp_= blur[y * width + x]; + float w_range =3D expf(-(intensity_diff * intensity_diff) = / (2 * sigma_range * sigma_range)); + + float weight =3D w * w_range; + sum +=3D weight * temp_blur[ny * width + x]; + sum_weights +=3D weight; + } + sigma[y * width + x] =3D (sum_weights > 0.0f) ? (sum / sum_wei= ghts) * hdr_scale : R_full[y * width + x]; + } + } + + av_free(temp_blur); + av_free(spatial_weights); +} +static void compute_local_adaptation_fast(const float *R_full, float *sigm= a, int width, int height, + float sigma_spatial, float sigma_range, float HDR_max) +{ + const int scale =3D 4; + int down_w =3D width / scale; + int down_h =3D height / scale; + int npix_small =3D down_w * down_h; + + // Allocating downsampled and result buffers + float *R_small =3D av_malloc(npix_small * sizeof(float)); + float *sigma_small =3D av_malloc(npix_small * sizeof(float)); + + // Simple box downsampling + for (int y =3D 0; y < down_h; y++) { + for (int x =3D 0; x < down_w; x++) { + float sum =3D 0.0f; + for (int dy =3D 0; dy < scale; dy++) { + for (int dx =3D 0; dx < scale; dx++) { + int sx =3D x * scale + dx; + int sy =3D y * scale + dy; + if (sx < width && sy < height) { + sum +=3D R_full[sy * width + sx]; + } + } + } + R_small[y * down_w + x] =3D sum / (scale * scale); + } + } + + compute_local_adaptation(R_small, sigma_small, down_w, down_h, sigma_s= patial, sigma_range, HDR_max); + + // Upsampling sigma_small -> sigma + struct SwsContext *sws_ctx =3D sws_getContext(down_w, down_h, AV_PIX_F= MT_GRAYF32, + width, height, AV_PIX_FMT_GRAYF32, + SWS_BILINEAR, NULL, NULL, NULL); + + const uint8_t *src[1] =3D {(uint8_t *)sigma_small}; + int src_linesize[1] =3D {down_w * sizeof(float)}; + uint8_t *dst[1] =3D {(uint8_t *)sigma}; + int dst_linesize[1] =3D {width * sizeof(float)}; + sws_scale(sws_ctx, src, src_linesize, 0, down_h, dst, dst_linesize); + + sws_freeContext(sws_ctx); + av_free(R_small); + av_free(sigma_small); +} + +static void compute_hdr_intensity(const float *R_full, const float *sigma,= float *I_hdr, + int width, int height, float n, float R_max) { + const float delta =3D 1e-6f; // 1e-6f + for (int y =3D 0; y < height; y++) { + for (int x =3D 0; x < width; x++) { + float R =3D R_full[y * width + x] / R_max; + R =3D fminf(R, 0.95f); + float denom =3D (1.0f - R) + delta; + I_hdr[y * width + x] =3D powf(R / denom, 1.0f / n) * sigma[y *= width + x]; + } + } +} + +static void inverse_tone_map_linear_rgb( + const float *R, const float *G, const float *B, + float *R_hdr, float *G_hdr, float *B_hdr, + int width, int height, FilterContext *s) +{ + int npix =3D width * height; + float *Y =3D av_malloc(npix * sizeof(float)); + float *Y_sigma =3D av_malloc(npix * sizeof(float)); + float *Y_hdr =3D av_malloc(npix * sizeof(float)); + + // Computing luminance (BT.2020) + for (int i =3D 0; i < npix; i++) + Y[i] =3D 0.2627f * R[i] + 0.6780f * G[i] + 0.0593f * B[i]; + + compute_local_adaptation_fast(Y, Y_sigma, width, height, + s->sigma_spatial, s->sigma_range, s->HDR_max); + + compute_hdr_intensity(Y, Y_sigma, Y_hdr, width, height, s->n, 1.0f); + + // Scaling RGB channels + for (int i =3D 0; i < npix; i++) { + float scale =3D Y_hdr[i] / (Y[i] + 1e-6f); + R_hdr[i] =3D R[i] * scale; + G_hdr[i] =3D G[i] * scale; + B_hdr[i] =3D B[i] * scale; + } + + av_free(Y); + av_free(Y_sigma); + av_free(Y_hdr); +} + +static void dither_pq_to_10bit(const float *pq, uint16_t *y_temp, int widt= h, int height) { + float *error =3D av_calloc(width * height, sizeof(float)); + for (int y =3D 0; y < height; y++) { + for (int x =3D 0; x < width; x++) { + int idx =3D y * width + x; + float val =3D fminf(fmaxf(pq[idx] + error[idx], 0.0f), 1.0f); + uint16_t q =3D (uint16_t)(val * 1023.0f + 0.5f); + y_temp[idx] =3D q; + float err =3D val - (q / 1023.0f); + if (x + 1 < width) error[idx + 1] +=3D err * 7.0f / 1= 6.0f; + if (y + 1 < height) { + if (x > 0) error[idx + width - 1] +=3D err * = 1.0f / 16.0f; + error[idx + width] +=3D err * = 5.0f / 16.0f; + if (x + 1 < width) error[idx + width + 1] +=3D err * = 3.0f / 16.0f; + } + } + } + av_free(error); +} + +static int fil_func(AVFilterLink *inlink, AVFrame *in) { + AVFilterContext *ctx =3D inlink->dst; + FilterContext *s =3D ctx->priv; + int width =3D in->width, height =3D in->height, npix =3D width * heigh= t; + + // Converting to RGB24 + struct SwsContext *sws_ctx =3D sws_getContext( + width, height, in->format, + width, height, AV_PIX_FMT_RGB24, + SWS_BILINEAR, NULL, NULL, NULL + ); + if (!sws_ctx) return AVERROR(ENOMEM); + + AVFrame *rgb_frame =3D av_frame_alloc(); + rgb_frame->format =3D AV_PIX_FMT_RGB24; + rgb_frame->width =3D width; + rgb_frame->height =3D height; + av_frame_get_buffer(rgb_frame, 32); + av_frame_copy_props(rgb_frame, in); + + sws_scale(sws_ctx, (const uint8_t *const *)in->data, in->linesize, 0, = height, + rgb_frame->data, rgb_frame->linesize); + sws_freeContext(sws_ctx); + + //Calling gamma lookup table initialization + init_bt709_gamma_lut(); + // Gamma linearization & gamut mapping + float *R =3D av_malloc(npix * sizeof(float)); + float *G =3D av_malloc(npix * sizeof(float)); + float *B =3D av_malloc(npix * sizeof(float)); + for (int y =3D 0; y < height; y++) { + uint8_t *row =3D rgb_frame->data[0] + y * rgb_frame->linesize[0]; + for (int x =3D 0; x < width; x++) { + float r =3D bt709_gamma_lut[row[x * 3 + 0]]; + float g =3D bt709_gamma_lut[row[x * 3 + 1]]; + float b =3D bt709_gamma_lut[row[x * 3 + 2]]; + R[y * width + x] =3D bt709_to_bt2020[0][0]*r + bt709_to_bt2020= [0][1]*g + bt709_to_bt2020[0][2]*b; + G[y * width + x] =3D bt709_to_bt2020[1][0]*r + bt709_to_bt2020= [1][1]*g + bt709_to_bt2020[1][2]*b; + B[y * width + x] =3D bt709_to_bt2020[2][0]*r + bt709_to_bt2020= [2][1]*g + bt709_to_bt2020[2][2]*b; + } + } + + // Inverse Tone Mapping + float *R_hdr =3D av_malloc(npix * sizeof(float)); + float *G_hdr =3D av_malloc(npix * sizeof(float)); + float *B_hdr =3D av_malloc(npix * sizeof(float)); + + inverse_tone_map_linear_rgb(R, G, B, R_hdr, G_hdr, B_hdr, width, heigh= t, s); + + float exposure =3D 3.5f; //4.0 + float contrast =3D 1.02f; //1.10 + float black =3D 0.03f, white =3D 8.0f; //0.04 //12.0 + float s_curve_pow =3D 1.00f; //0.70 + + for (int i =3D 0; i < npix; i++) { + float r =3D R[i] * exposure, g =3D G[i] * exposure, b =3D B[i] * e= xposure; + float lum =3D 0.2627f * r + 0.6780f * g + 0.0593f * b; + + + float tm =3D (lum - black) / (white - black + 1e-6f); + tm =3D fmaxf(fminf(tm, 1.0f), 0.0f); + tm =3D powf(tm, s_curve_pow); + + float scale =3D (lum > 1e-6f) ? tm / lum : 0.0f; + r *=3D scale; + g *=3D scale; + b *=3D scale; + + // Contrast + r =3D (r - 0.5f) * contrast + 0.5f; + g =3D (g - 0.5f) * contrast + 0.5f; + b =3D (b - 0.5f) * contrast + 0.5f; + + // White balance correction + float white_balance_r =3D 1.0f; + float white_balance_g =3D 1.0f; + float white_balance_b =3D 1.1f; + + r *=3D white_balance_r; + g *=3D white_balance_g; + b *=3D white_balance_b; + + // Final clamp + r =3D fminf(fmaxf(r, 0.0f), 1.0f); + g =3D fminf(fmaxf(g, 0.0f), 1.0f); + b =3D fminf(fmaxf(b, 0.0f), 1.0f); + + R_hdr[i] =3D r; + G_hdr[i] =3D g; + B_hdr[i] =3D b; + } + + //Computing linear luminance (BT.2020) + float *Y =3D av_malloc(npix * sizeof(float)); + for (int i =3D 0; i < npix; i++) + Y[i] =3D 0.2627f * R_hdr[i] + 0.6780f * G_hdr[i] + 0.0593f * B_hdr= [i]; + + // Applying PQ transfer + float *pq =3D av_malloc(npix * sizeof(float)); + for (int i =3D 0; i < npix; i++) { + pq[i] =3D linear_to_pq(Y[i] * (10000.0f / s->HDR_max)); + pq[i] =3D fminf(fmaxf(pq[i], 0.0f), 1.0f); + } + //Allocating output frame (YUV420P10LE) + AVFrame *out =3D av_frame_alloc(); + out->format =3D AV_PIX_FMT_YUV420P10LE; + out->width =3D width; + out->height =3D height; + av_frame_get_buffer(out, 32); + av_frame_copy_props(out, in); + + // Setting HDR metadata + out->color_primaries =3D AVCOL_PRI_BT2020; + out->color_trc =3D AVCOL_TRC_SMPTE2084; // PQ + out->colorspace =3D AVCOL_SPC_BT2020_NCL; + out->color_range =3D AVCOL_RANGE_MPEG; // Full range for HDR + + // Setting mastering display metadata if available + if (out->metadata) { + // Mastering display primaries (BT.2020) + av_dict_set(&out->metadata, "mastering_display_primaries", + "0.708,0.292,0.170,0.797,0.131,0.046,0.3127,0.3290", 0)= ; + + // Mastering display luminance (in nits) + char luminance_str[64]; + snprintf(luminance_str, sizeof(luminance_str), "%.1f,%.1f", + s->HDR_max, 0.001f); // Max luminance, min luminance + av_dict_set(&out->metadata, "mastering_display_luminance", luminan= ce_str, 0); + + // Content light level + char content_light_str[64]; + float max_content_light =3D 0.0f; + for (int i =3D 0; i < npix; i++) { + max_content_light =3D fmaxf(max_content_light, Y[i]); + } + max_content_light =3D linear_to_pq(max_content_light * (10000.0f /= s->HDR_max)) * 10000.0f; + snprintf(content_light_str, sizeof(content_light_str), "%.0f,%.0f"= , + max_content_light, max_content_light * 0.5f); + av_dict_set(&out->metadata, "content_light_level", content_light_s= tr, 0); + } + + // Clearing Y, U, V planes + av_frame_make_writable(out); + for (int y =3D 0; y < height; y++) + memset(out->data[0] + y * out->linesize[0], 0, out->linesize[0]); + + // Dithering PQ to 10-bit Y plane + uint16_t *y_temp =3D av_malloc(npix * sizeof(uint16_t)); + dither_pq_to_10bit(pq, y_temp, width, height); + for (int y =3D 0; y < height; y++) { + uint16_t *row =3D (uint16_t *)(out->data[0] + y * out->linesize[0]= ); + memcpy(row, y_temp + y * width, width * sizeof(uint16_t)); + } + + // Computing and encoding U, V chroma planes from original SDR RGB + for (int y =3D 0; y < height / 2; y++) { + uint16_t *u_row =3D (uint16_t *)(out->data[1] + y * out->linesize[= 1]); + uint16_t *v_row =3D (uint16_t *)(out->data[2] + y * out->linesize[= 2]); + for (int x =3D 0; x < width / 2; x++) { + float r =3D 0.0f, g =3D 0.0f, b =3D 0.0f; + for (int dy =3D 0; dy < 2; dy++) { + for (int dx =3D 0; dx < 2; dx++) { + int src_x =3D x * 2 + dx; + int src_y =3D y * 2 + dy; + if (src_x < width && src_y < height) { + int idx =3D src_y * width + src_x; + r +=3D R[idx]; + g +=3D G[idx]; + b +=3D B[idx]; + } + } + } + r /=3D 4.0f; g /=3D 4.0f; b /=3D 4.0f; + float Y_sdr =3D 0.2627f * r + 0.6780f * g + 0.0593f * b; + float U =3D (b - Y_sdr) / 1.8814f; + float V =3D (r - Y_sdr) / 1.4746f; + float chroma_blend =3D 1.0f - fminf(Y_sdr, 1.0f); + float chroma_boost =3D 0.85f * chroma_blend + 0.85f * (1.0f - = chroma_blend); //0.8 + // float chroma_boost =3D 1.08f; + U *=3D chroma_boost; + V *=3D chroma_boost; + U =3D fmaxf(fminf(U, 0.45f), -0.45f); + V =3D fmaxf(fminf(V, 0.45f), -0.45f); + u_row[x] =3D (uint16_t)roundf(U * 512.0f + 512.0f); + v_row[x] =3D (uint16_t)roundf(V * 512.0f + 512.0f); + } + } + + av_free(R); av_free(G); av_free(B); + av_free(R_hdr); av_free(G_hdr); av_free(B_hdr); + av_free(Y); av_free(pq); av_free(y_temp); + av_frame_free(&rgb_frame); + av_frame_free(&in); + + return ff_filter_frame(ctx->outputs[0], out); +} + +static const AVOption fil_options[] =3D { + { "sigma_spatial", "Spatial sigma", OFFSET(sigma_spatial), AV_OPT_TYPE= _FLOAT, {.dbl=3D2.0}, 1.0, 50.0, FLAGS }, + { "sigma_range", "Range sigma", OFFSET(sigma_range), AV_OPT_TYPE_FLOAT= , {.dbl=3D0.3}, 0.01, 1.0, FLAGS }, + { "n", "Sensitivity exponent", OFFSET(n), AV_OPT_TYPE_FLOAT, {.dbl=3D0= .9}, 0.5, 2.0, FLAGS }, + { "hdr_max", "Peak HDR luminance", OFFSET(HDR_max), AV_OPT_TYPE_FLOAT,= {.dbl=3D25.0}, 10.0, 10000.0, FLAGS }, + { NULL } +}; + +AVFILTER_DEFINE_CLASS(fil); + +static const AVFilterPad fil_inputs[] =3D { + { + .name =3D "default", + .type =3D AVMEDIA_TYPE_VIDEO, + .filter_frame =3D fil_func, + } +}; + +static const AVFilterPad fil_outputs[] =3D { + { + .name =3D "default", + .type =3D AVMEDIA_TYPE_VIDEO, + } +}; + +static int ff_filter_init(AVFilterContext *avctx) { + av_log_set_level(AV_LOG_DEBUG); + av_log(avctx, AV_LOG_INFO, "Initializing filter with 1 input and 1 out= put\n"); + return 0; +} + +static void ff_filter_uninit(AVFilterContext *avctx) +{ + +} + +const FFFilter ff_vf_inversetonemap =3D { + .p.name =3D "inversetonemap", + .p.description =3D "SDR to HDR inverse tone mapping filter", + .p.priv_class =3D &fil_class, + .p.flags =3D AVFILTER_FLAG_SLICE_THREADS, + .priv_size =3D sizeof(FilterContext), + .init =3D &ff_filter_init, + .uninit =3D &ff_filter_uninit, + FILTER_INPUTS(fil_inputs), + FILTER_OUTPUTS(fil_outputs), + FILTER_PIXFMTS(AV_PIX_FMT_YUV420P10LE), +}; + + + + + + + + -- 2.49.0 Get Outlook for Mac --===============8269019266584589001== Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Content-Disposition: inline _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe". --===============8269019266584589001==--