From patchwork Sat May 2 22:57:00 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 3672 Return-Path: Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 278BA61618 for ; Sun, 3 May 2020 00:57:11 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="I9wiytJo"; 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 B0A1072C for ; Sun, 3 May 2020 00:57:10 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1588460230; bh=LFpeFFcYuEH8q6W395WuQPrsDgSAIsN2yempIkqWXeU=; h=From:To:Subject:Date:In-Reply-To:References:From; b=I9wiytJoFI1dDayO3CaIABnXNxaPGHFqquEnvvT11f/tuYEcaCYOJ1EM2a1V0mSUw 6H/1mtALgG+2s5MSanknXc7RZAex514qwtPAlu2FKDl9a6QGaTH8sUq+z/+3QaajFY +gzIdt8SxR28aINIyaeVTJt0WJ34cIWinoRaqrks= From: Laurent Pinchart To: libcamera-devel@lists.libcamera.org Date: Sun, 3 May 2020 01:57:00 +0300 Message-Id: <20200502225704.2911-2-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 1/5] qcam: Pass request metadata to DNG writer 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:11 -0000 The DNG writer will use the metadata to populate DNG tags. Signed-off-by: Laurent Pinchart Reviewed-by: Niklas Söderlund --- src/qcam/dng_writer.cpp | 1 + src/qcam/dng_writer.h | 2 ++ src/qcam/main_window.cpp | 18 +++++++++--------- src/qcam/main_window.h | 22 ++++++++++++++++++++-- 4 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/qcam/dng_writer.cpp b/src/qcam/dng_writer.cpp index 7d51965dc2ea..b1984c6647dd 100644 --- a/src/qcam/dng_writer.cpp +++ b/src/qcam/dng_writer.cpp @@ -101,6 +101,7 @@ static const std::map formatInfo = { int DNGWriter::write(const char *filename, const Camera *camera, const StreamConfiguration &config, + const ControlList &metadata, const FrameBuffer *buffer, const void *data) { const auto it = formatInfo.find(config.pixelFormat); diff --git a/src/qcam/dng_writer.h b/src/qcam/dng_writer.h index 88b218753a1a..d74d73d06a14 100644 --- a/src/qcam/dng_writer.h +++ b/src/qcam/dng_writer.h @@ -12,6 +12,7 @@ #include #include +#include #include using namespace libcamera; @@ -21,6 +22,7 @@ class DNGWriter public: static int write(const char *filename, const Camera *camera, const StreamConfiguration &config, + const ControlList &metadata, const FrameBuffer *buffer, const void *data); }; diff --git a/src/qcam/main_window.cpp b/src/qcam/main_window.cpp index 8720c6c61f40..7de089571234 100644 --- a/src/qcam/main_window.cpp +++ b/src/qcam/main_window.cpp @@ -549,7 +549,7 @@ void MainWindow::captureRaw() captureRaw_ = true; } -void MainWindow::processRaw(FrameBuffer *buffer) +void MainWindow::processRaw(FrameBuffer *buffer, const ControlList &metadata) { #ifdef HAVE_DNG QString defaultPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); @@ -559,7 +559,7 @@ void MainWindow::processRaw(FrameBuffer *buffer) if (!filename.isEmpty()) { const MappedBuffer &mapped = mappedBuffers_[buffer]; DNGWriter::write(filename.toStdString().c_str(), camera_.get(), - rawStream_->configuration(), buffer, + rawStream_->configuration(), metadata, buffer, mapped.memory); } #endif @@ -586,7 +586,7 @@ void MainWindow::requestComplete(Request *request) */ { QMutexLocker locker(&mutex_); - doneQueue_.enqueue(request->buffers()); + doneQueue_.enqueue({ request->buffers(), request->metadata() }); } QCoreApplication::postEvent(this, new CaptureEvent); @@ -599,22 +599,22 @@ void MainWindow::processCapture() * if stopCapture() has been called while a CaptureEvent was posted but * not processed yet. Return immediately in that case. */ - std::map buffers; + CaptureRequest request; { QMutexLocker locker(&mutex_); if (doneQueue_.isEmpty()) return; - buffers = doneQueue_.dequeue(); + request = doneQueue_.dequeue(); } /* Process buffers. */ - if (buffers.count(vfStream_)) - processViewfinder(buffers[vfStream_]); + if (request.buffers_.count(vfStream_)) + processViewfinder(request.buffers_[vfStream_]); - if (buffers.count(rawStream_)) - processRaw(buffers[rawStream_]); + if (request.buffers_.count(rawStream_)) + processRaw(request.buffers_[rawStream_], request.metadata_); } void MainWindow::processViewfinder(FrameBuffer *buffer) diff --git a/src/qcam/main_window.h b/src/qcam/main_window.h index 295ecc537e9d..59fa2d985655 100644 --- a/src/qcam/main_window.h +++ b/src/qcam/main_window.h @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -37,6 +38,23 @@ enum { OptStream = 's', }; +class CaptureRequest +{ +public: + CaptureRequest() + { + } + + CaptureRequest(const std::map &buffers, + const ControlList &metadata) + : buffers_(buffers), metadata_(metadata) + { + } + + std::map buffers_; + ControlList metadata_; +}; + class MainWindow : public QMainWindow { Q_OBJECT @@ -56,7 +74,7 @@ private Q_SLOTS: void saveImageAs(); void captureRaw(); - void processRaw(FrameBuffer *buffer); + void processRaw(FrameBuffer *buffer, const ControlList &metadata); void queueRequest(FrameBuffer *buffer); @@ -103,7 +121,7 @@ private: Stream *vfStream_; Stream *rawStream_; std::map> freeBuffers_; - QQueue> doneQueue_; + QQueue doneQueue_; QMutex mutex_; /* Protects freeBuffers_ and doneQueue_ */ uint64_t lastBufferTime_; From patchwork Sat May 2 22:57:01 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 3673 Return-Path: Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 620796162E for ; Sun, 3 May 2020 00:57:11 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="YUDiKhqP"; 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 0B207521 for ; Sun, 3 May 2020 00:57:10 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1588460231; bh=wzQtGrsiDCZBzEFWkssCYnZH/nvfajppgb4PpeXrX5M=; h=From:To:Subject:Date:In-Reply-To:References:From; b=YUDiKhqPRsKSUWir5tey7Y5YgO++BwbYUTVB9PsxtB3FEAfM1asyKArCnkqzEBJgr c9YcaNA9oPFbli+bM3CdOtmj4iwlgHK+Kic9iiaLJSbIozIO2+MYH74xUD6txXuR4s wzbdzOwopqAMuCOOx2/1P9j6TTgmisXvG5uWSTus= From: Laurent Pinchart To: libcamera-devel@lists.libcamera.org Date: Sun, 3 May 2020 01:57:01 +0300 Message-Id: <20200502225704.2911-3-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 2/5] qcam: dng_writer: Name arguments to packScanline() 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:11 -0000 Name arguments to the FormatInfo::packScanline function pointer to make it easier to understand its usage when reading the function declaration. Signed-off-by: Laurent Pinchart Reviewed-by: Niklas Söderlund --- src/qcam/dng_writer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/qcam/dng_writer.cpp b/src/qcam/dng_writer.cpp index b1984c6647dd..d5270323abd4 100644 --- a/src/qcam/dng_writer.cpp +++ b/src/qcam/dng_writer.cpp @@ -23,7 +23,8 @@ enum CFAPatternColour : uint8_t { struct FormatInfo { uint8_t bitsPerSample; CFAPatternColour pattern[4]; - void (*packScanline)(void *, const void *, unsigned int); + void (*packScanline)(void *output, const void *input, + unsigned int width); }; void packScanlineSBGGR10P(void *output, const void *input, unsigned int width) From patchwork Sat May 2 22:57:02 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 3674 Return-Path: Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id BC61E6162E for ; Sun, 3 May 2020 00:57:11 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="gy7TcazE"; 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 582FB72C 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=xnEC6zKrNN2kCkjSrIEpDNfHBLCq+Yw9i379p1zjxU4=; h=From:To:Subject:Date:In-Reply-To:References:From; b=gy7TcazEdPij1YVGdRwy3hfJoL1ZQzRSICg0iaW2LZxcFHMOWHBHTnuHj2HmZwR8c ZtagjA6bI+0ocd7ieH+keHP2cSuZPxQ0O1F/UfGj1D+4SP5vXu/ZwmkfMUIPAEybqv srSTgtNKRpTVwHfFoG9WVe2mdfgZsZG9yzeoxBT0= From: Laurent Pinchart To: libcamera-devel@lists.libcamera.org Date: Sun, 3 May 2020 01:57:02 +0300 Message-Id: <20200502225704.2911-4-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 3/5] qcam: dng_writer: Output thumbnail 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 Generate a greyscale, 1/16 resolution thumbnail and add it to the DNG file. This requires shuffling the RAW image generation as the thumbnail has to be stored in the main IFD as per the DNG and TIFF/EP specifications. Signed-off-by: Laurent Pinchart Reviewed-by: Niklas Söderlund --- src/qcam/dng_writer.cpp | 126 +++++++++++++++++++++++++++++++++++----- 1 file changed, 111 insertions(+), 15 deletions(-) diff --git a/src/qcam/dng_writer.cpp b/src/qcam/dng_writer.cpp index d5270323abd4..e55985ba86e7 100644 --- a/src/qcam/dng_writer.cpp +++ b/src/qcam/dng_writer.cpp @@ -25,6 +25,9 @@ struct FormatInfo { CFAPatternColour pattern[4]; void (*packScanline)(void *output, const void *input, unsigned int width); + void (*thumbScanline)(const FormatInfo &info, void *output, + const void *input, unsigned int width, + unsigned int stride); }; void packScanlineSBGGR10P(void *output, const void *input, unsigned int width) @@ -57,46 +60,70 @@ void packScanlineSBGGR12P(void *output, const void *input, unsigned int width) } } +void thumbScanlineSBGGRxxP(const FormatInfo &info, void *output, + const void *input, unsigned int width, + unsigned int stride) +{ + const uint8_t *in = static_cast(input); + uint8_t *out = static_cast(output); + + /* Number of bytes corresponding to 16 pixels. */ + unsigned int skip = info.bitsPerSample * 16 / 8; + + for (unsigned int x = 0; x < width; x++) { + *out++ = (in[0] + in[1] + in[stride] + in[stride + 1]) >> 2; + in += skip; + } +} + static const std::map formatInfo = { { PixelFormat(DRM_FORMAT_SBGGR10, MIPI_FORMAT_MOD_CSI2_PACKED), { .bitsPerSample = 10, .pattern = { CFAPatternBlue, CFAPatternGreen, CFAPatternGreen, CFAPatternRed }, .packScanline = packScanlineSBGGR10P, + .thumbScanline = thumbScanlineSBGGRxxP, } }, { PixelFormat(DRM_FORMAT_SGBRG10, MIPI_FORMAT_MOD_CSI2_PACKED), { .bitsPerSample = 10, .pattern = { CFAPatternGreen, CFAPatternBlue, CFAPatternRed, CFAPatternGreen }, .packScanline = packScanlineSBGGR10P, + .thumbScanline = thumbScanlineSBGGRxxP, } }, { PixelFormat(DRM_FORMAT_SGRBG10, MIPI_FORMAT_MOD_CSI2_PACKED), { .bitsPerSample = 10, .pattern = { CFAPatternGreen, CFAPatternRed, CFAPatternBlue, CFAPatternGreen }, .packScanline = packScanlineSBGGR10P, + .thumbScanline = thumbScanlineSBGGRxxP, } }, { PixelFormat(DRM_FORMAT_SRGGB10, MIPI_FORMAT_MOD_CSI2_PACKED), { .bitsPerSample = 10, .pattern = { CFAPatternRed, CFAPatternGreen, CFAPatternGreen, CFAPatternBlue }, .packScanline = packScanlineSBGGR10P, + .thumbScanline = thumbScanlineSBGGRxxP, } }, { PixelFormat(DRM_FORMAT_SBGGR12, MIPI_FORMAT_MOD_CSI2_PACKED), { .bitsPerSample = 12, .pattern = { CFAPatternBlue, CFAPatternGreen, CFAPatternGreen, CFAPatternRed }, .packScanline = packScanlineSBGGR12P, + .thumbScanline = thumbScanlineSBGGRxxP, } }, { PixelFormat(DRM_FORMAT_SGBRG12, MIPI_FORMAT_MOD_CSI2_PACKED), { .bitsPerSample = 12, .pattern = { CFAPatternGreen, CFAPatternBlue, CFAPatternRed, CFAPatternGreen }, .packScanline = packScanlineSBGGR12P, + .thumbScanline = thumbScanlineSBGGRxxP, } }, { PixelFormat(DRM_FORMAT_SGRBG12, MIPI_FORMAT_MOD_CSI2_PACKED), { .bitsPerSample = 12, .pattern = { CFAPatternGreen, CFAPatternRed, CFAPatternBlue, CFAPatternGreen }, .packScanline = packScanlineSBGGR12P, + .thumbScanline = thumbScanlineSBGGRxxP, } }, { PixelFormat(DRM_FORMAT_SRGGB12, MIPI_FORMAT_MOD_CSI2_PACKED), { .bitsPerSample = 12, .pattern = { CFAPatternRed, CFAPatternGreen, CFAPatternGreen, CFAPatternBlue }, .packScanline = packScanlineSBGGR12P, + .thumbScanline = thumbScanlineSBGGRxxP, } }, }; @@ -118,40 +145,101 @@ int DNGWriter::write(const char *filename, const Camera *camera, return -EINVAL; } + /* + * Scanline buffer, has to be large enough to store both a RAW scanline + * or a thumbnail scanline. The latter will always be much smaller than + * the former as we downscale by 16 in both directions. + */ + uint8_t scanline[(config.size.width * info->bitsPerSample + 7) / 8]; + + toff_t rawIFDOffset = 0; + + /* + * Start with a thumbnail in IFD 0 for compatibility with TIFF baseline + * readers, as required by the TIFF/EP specification. Tags that apply to + * the whole file are stored here. + */ const uint8_t version[] = { 1, 2, 0, 0 }; - const uint16_t cfa_repeatpatterndim[] = { 2, 2 }; + TIFFSetField(tif, TIFFTAG_DNGVERSION, version); TIFFSetField(tif, TIFFTAG_DNGBACKWARDVERSION, version); - TIFFSetField(tif, TIFFTAG_SUBFILETYPE, 0); - TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, config.size.width); - TIFFSetField(tif, TIFFTAG_IMAGELENGTH, config.size.height); - TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, info->bitsPerSample); - TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE); - TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_CFA); TIFFSetField(tif, TIFFTAG_FILLORDER, FILLORDER_MSB2LSB); TIFFSetField(tif, TIFFTAG_MAKE, "libcamera"); TIFFSetField(tif, TIFFTAG_MODEL, camera->name().c_str()); TIFFSetField(tif, TIFFTAG_UNIQUECAMERAMODEL, camera->name().c_str()); - TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); - TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1); - TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); TIFFSetField(tif, TIFFTAG_SOFTWARE, "qcam"); + TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); + + /* + * Thumbnail-specific tags. The thumbnail is stored as a greyscale image + * with 1/16 of the raw image resolution. + */ + TIFFSetField(tif, TIFFTAG_SUBFILETYPE, FILETYPE_REDUCEDIMAGE); + TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, config.size.width / 16); + TIFFSetField(tif, TIFFTAG_IMAGELENGTH, config.size.height / 16); + TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8); + TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE); + TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK); + TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1); + TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); TIFFSetField(tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT); - TIFFSetField(tif, TIFFTAG_CFAREPEATPATTERNDIM, cfa_repeatpatterndim); + + /* + * Reserve space for the SubIFD tag, pointing to the IFD for the raw + * image. The real offset will be set later. + */ + TIFFSetField(tif, TIFFTAG_SUBIFD, 1, &rawIFDOffset); + + /* Write the thumbnail. */ + const uint8_t *row = static_cast(data); + for (unsigned int y = 0; y < config.size.height / 16; y++) { + info->thumbScanline(*info, &scanline, row, + config.size.width / 16, config.stride); + + if (TIFFWriteScanline(tif, &scanline, y, 0) != 1) { + std::cerr << "Failed to write thumbnail scanline" + << std::endl; + TIFFClose(tif); + return -EINVAL; + } + + row += config.stride * 16; + } + + TIFFWriteDirectory(tif); + + /* Create a new IFD for the RAW image. */ + const uint16_t cfaRepeatPatternDim[] = { 2, 2 }; + const uint8_t cfaPlaneColor[] = { + CFAPatternRed, + CFAPatternGreen, + CFAPatternBlue + }; + + TIFFSetField(tif, TIFFTAG_SUBFILETYPE, 0); + TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, config.size.width); + TIFFSetField(tif, TIFFTAG_IMAGELENGTH, config.size.height); + TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, info->bitsPerSample); + TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE); + TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_CFA); + TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1); + TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + TIFFSetField(tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT); + TIFFSetField(tif, TIFFTAG_CFAREPEATPATTERNDIM, cfaRepeatPatternDim); TIFFSetField(tif, TIFFTAG_CFAPATTERN, info->pattern); - TIFFSetField(tif, TIFFTAG_CFAPLANECOLOR, 3, "\00\01\02"); + TIFFSetField(tif, TIFFTAG_CFAPLANECOLOR, 3, cfaPlaneColor); TIFFSetField(tif, TIFFTAG_CFALAYOUT, 1); /* \todo Add more EXIF fields to output. */ /* Write RAW content. */ - const uint8_t *row = static_cast(data); - uint8_t scanline[(config.size.width * info->bitsPerSample + 7) / 8]; + row = static_cast(data); for (unsigned int y = 0; y < config.size.height; y++) { info->packScanline(&scanline, row, config.size.width); if (TIFFWriteScanline(tif, &scanline, y, 0) != 1) { - std::cerr << "Failed to write scanline" << std::endl; + std::cerr << "Failed to write RAW scanline" + << std::endl; TIFFClose(tif); return -EINVAL; } @@ -159,6 +247,14 @@ int DNGWriter::write(const char *filename, const Camera *camera, row += config.stride; } + /* Checkpoint the IFD to retrieve its offset, and write it out. */ + TIFFCheckpointDirectory(tif); + rawIFDOffset = TIFFCurrentDirOffset(tif); + TIFFWriteDirectory(tif); + + /* Update the IFD offsets and close the file. */ + TIFFSetDirectory(tif, 0); + TIFFSetField(tif, TIFFTAG_SUBIFD, 1, &rawIFDOffset); TIFFWriteDirectory(tif); TIFFClose(tif); 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); From patchwork Sat May 2 22:57:04 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Laurent Pinchart X-Patchwork-Id: 3676 Return-Path: Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 5EB9D6162E 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="d1aoB6+j"; 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 023F472C 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=1588460232; bh=WBjePhcF60gvK3H85ZX8vDSpxEG6HKuCVjORCGZbi4A=; h=From:To:Subject:Date:In-Reply-To:References:From; b=d1aoB6+jD7ZS0Qqc9dEy7/AsguLM7uiEBsoz5c4zcUn9Sz3NvF79jq8vpRBLrEEiL kTW2v+CLlmLRPu2hVL4+yc4zMGa4II0ya2N3QZqDmU729AyA3Pen8pScqCw1Wsh2vS IGUh5SKbArfiflDigugFLfIud/EEmXfmxS4GT/Zo= From: Laurent Pinchart To: libcamera-devel@lists.libcamera.org Date: Sun, 3 May 2020 01:57:04 +0300 Message-Id: <20200502225704.2911-6-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 5/5] qcam: dng_writer: Generate thumbnail in RGB format 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:13 -0000 While the DNG specification supports greyscale ("BlackIsZero") for thumbnails, RawTherapee seems to have trouble reading them. Generate thumbnails in RGB instead (but still with greyscale content, to avoid having to interpolate colour components). Signed-off-by: Laurent Pinchart Reviewed-by: Niklas Söderlund --- src/qcam/dng_writer.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/qcam/dng_writer.cpp b/src/qcam/dng_writer.cpp index d04a8e161218..bc1a63e69799 100644 --- a/src/qcam/dng_writer.cpp +++ b/src/qcam/dng_writer.cpp @@ -74,7 +74,10 @@ void thumbScanlineSBGGRxxP(const FormatInfo &info, void *output, unsigned int skip = info.bitsPerSample * 16 / 8; for (unsigned int x = 0; x < width; x++) { - *out++ = (in[0] + in[1] + in[stride] + in[stride + 1]) >> 2; + uint8_t value = (in[0] + in[1] + in[stride] + in[stride + 1]) >> 2; + *out++ = value; + *out++ = value; + *out++ = value; in += skip; } } @@ -175,16 +178,17 @@ int DNGWriter::write(const char *filename, const Camera *camera, TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); /* - * Thumbnail-specific tags. The thumbnail is stored as a greyscale image - * with 1/16 of the raw image resolution. + * Thumbnail-specific tags. The thumbnail is stored as an RGB image + * with 1/16 of the raw image resolution. Greyscale would save space, + * but doesn't seem well supported by RawTherapee. */ TIFFSetField(tif, TIFFTAG_SUBFILETYPE, FILETYPE_REDUCEDIMAGE); TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, config.size.width / 16); TIFFSetField(tif, TIFFTAG_IMAGELENGTH, config.size.height / 16); TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8); TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE); - TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK); - TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1); + TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); + TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3); TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); TIFFSetField(tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT);