From patchwork Sat May 2 22:57:03 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 3675 Return-Path: Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 0D18D61618 for ; Sun, 3 May 2020 00:57:12 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="mbWhJPVY"; dkim-atps=neutral Received: from pendragon.bb.dnainternet.fi (81-175-216-236.bb.dnainternet.fi [81.175.216.236]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id A92D3521 for ; Sun, 3 May 2020 00:57:11 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1588460231; bh=UuBQR8QUttSEpos4JV/OxaP/5df2hGZjORzutizPPaI=; h=From:To:Subject:Date:In-Reply-To:References:From; b=mbWhJPVY6zAmaHsT3BBvVzJrvOJ/MppNslea68MwJTPMXNgQ67DrH7IecjuJ1ZJN8 VID4RWGq3j11AI6xR6DLDCEvQ/93RhHNisEak3DKR3DyNs1rXk3iHRtMC8Fr1T9WUW Ly4mi9Cn+2DNoBSDksd2OuxmyPkL/XdZv8DbM2N0= From: Laurent Pinchart To: libcamera-devel@lists.libcamera.org Date: Sun, 3 May 2020 01:57:03 +0300 Message-Id: <20200502225704.2911-5-laurent.pinchart@ideasonboard.com> X-Mailer: git-send-email 2.25.3 In-Reply-To: <20200502225704.2911-1-laurent.pinchart@ideasonboard.com> References: <20200502225704.2911-1-laurent.pinchart@ideasonboard.com> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH 4/5] qcam: dng_writer: Populate DNG tags from metadata X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Sat, 02 May 2020 22:57:12 -0000 Populate the DNG black level, ISO speed rating and exposure time from metadata. The ISO speed rating and exposure time are standardized as EXIF tags, not TIFF tags, and require a separate EXIF IFD. Signed-off-by: Laurent Pinchart Reviewed-by: Niklas Söderlund --- src/qcam/dng_writer.cpp | 71 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 68 insertions(+), 3 deletions(-) diff --git a/src/qcam/dng_writer.cpp b/src/qcam/dng_writer.cpp index e55985ba86e7..d04a8e161218 100644 --- a/src/qcam/dng_writer.cpp +++ b/src/qcam/dng_writer.cpp @@ -7,11 +7,14 @@ #include "dng_writer.h" +#include #include #include #include +#include + using namespace libcamera; enum CFAPatternColour : uint8_t { @@ -153,6 +156,7 @@ int DNGWriter::write(const char *filename, const Camera *camera, uint8_t scanline[(config.size.width * info->bitsPerSample + 7) / 8]; toff_t rawIFDOffset = 0; + toff_t exifIFDOffset = 0; /* * Start with a thumbnail in IFD 0 for compatibility with TIFF baseline @@ -185,10 +189,12 @@ int DNGWriter::write(const char *filename, const Camera *camera, TIFFSetField(tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT); /* - * Reserve space for the SubIFD tag, pointing to the IFD for the raw - * image. The real offset will be set later. + * Reserve space for the SubIFD and ExifIFD tags, pointing to the IFD + * for the raw image and EXIF data respectively. The real offsets will + * be set later. */ TIFFSetField(tif, TIFFTAG_SUBIFD, 1, &rawIFDOffset); + TIFFSetField(tif, TIFFTAG_EXIFIFD, exifIFDOffset); /* Write the thumbnail. */ const uint8_t *row = static_cast(data); @@ -230,7 +236,47 @@ int DNGWriter::write(const char *filename, const Camera *camera, TIFFSetField(tif, TIFFTAG_CFAPLANECOLOR, 3, cfaPlaneColor); TIFFSetField(tif, TIFFTAG_CFALAYOUT, 1); - /* \todo Add more EXIF fields to output. */ + const uint16_t blackLevelRepeatDim[] = { 2, 2 }; + float blackLevel[] = { 0.0f, 0.0f, 0.0f, 0.0f }; + uint32_t whiteLevel = (1 << info->bitsPerSample) - 1; + + if (metadata.contains(controls::SensorBlackLevels)) { + Span levels = metadata.get(controls::SensorBlackLevels); + + /* + * The black levels control is specified in R, Gr, Gb, B order. + * Map it to the TIFF tag that is specified in CFA pattern + * order. + */ + unsigned int green = (info->pattern[0] == CFAPatternRed || + info->pattern[1] == CFAPatternRed) + ? 0 : 1; + + for (unsigned int i = 0; i < 4; ++i) { + unsigned int level; + + switch (info->pattern[i]) { + case CFAPatternRed: + level = levels[0]; + break; + case CFAPatternGreen: + level = levels[green + 1]; + green = (green + 1) % 2; + break; + case CFAPatternBlue: + default: + level = levels[3]; + break; + } + + /* Map the 16-bit value to the bits per sample range. */ + blackLevel[i] = level >> (16 - info->bitsPerSample); + } + } + + TIFFSetField(tif, TIFFTAG_BLACKLEVELREPEATDIM, &blackLevelRepeatDim); + TIFFSetField(tif, TIFFTAG_BLACKLEVEL, 4, &blackLevel); + TIFFSetField(tif, TIFFTAG_WHITELEVEL, 1, &whiteLevel); /* Write RAW content. */ row = static_cast(data); @@ -252,9 +298,28 @@ int DNGWriter::write(const char *filename, const Camera *camera, rawIFDOffset = TIFFCurrentDirOffset(tif); TIFFWriteDirectory(tif); + /* Create a new IFD for the EXIF data and fill it. */ + TIFFCreateEXIFDirectory(tif); + + if (metadata.contains(controls::AnalogueGain)) { + float gain = metadata.get(controls::AnalogueGain); + uint16_t iso = std::min(std::max(gain * 100, 0.0f), 65535.0f); + TIFFSetField(tif, EXIFTAG_ISOSPEEDRATINGS, 1, &iso); + } + + if (metadata.contains(controls::ExposureTime)) { + float exposureTime = metadata.get(controls::ExposureTime) / 1e6; + TIFFSetField(tif, EXIFTAG_EXPOSURETIME, exposureTime); + } + + TIFFCheckpointDirectory(tif); + exifIFDOffset = TIFFCurrentDirOffset(tif); + TIFFWriteDirectory(tif); + /* Update the IFD offsets and close the file. */ TIFFSetDirectory(tif, 0); TIFFSetField(tif, TIFFTAG_SUBIFD, 1, &rawIFDOffset); + TIFFSetField(tif, TIFFTAG_EXIFIFD, exifIFDOffset); TIFFWriteDirectory(tif); TIFFClose(tif);