From patchwork Fri May 1 15:27:44 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Niklas_S=C3=B6derlund?= X-Patchwork-Id: 3653 Return-Path: Received: from vsp-unauthed02.binero.net (vsp-unauthed02.binero.net [195.74.38.227]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 95697614EF for ; Fri, 1 May 2020 17:28:06 +0200 (CEST) X-Halon-ID: 5a093066-8bc0-11ea-aeed-005056917f90 Authorized-sender: niklas@soderlund.pp.se Received: from bismarck.berto.se (p4fca2392.dip0.t-ipconnect.de [79.202.35.146]) by bin-vsp-out-02.atm.binero.net (Halon) with ESMTPA id 5a093066-8bc0-11ea-aeed-005056917f90; Fri, 01 May 2020 17:28:05 +0200 (CEST) From: =?utf-8?q?Niklas_S=C3=B6derlund?= To: libcamera-devel@lists.libcamera.org Date: Fri, 1 May 2020 17:27:44 +0200 Message-Id: <20200501152745.437777-3-niklas.soderlund@ragnatech.se> X-Mailer: git-send-email 2.26.2 In-Reply-To: <20200501152745.437777-1-niklas.soderlund@ragnatech.se> References: <20200501152745.437777-1-niklas.soderlund@ragnatech.se> MIME-Version: 1.0 Subject: [libcamera-devel] [PATCH v2 2/3] qcam: Add DNGWriter 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: Fri, 01 May 2020 15:28:07 -0000 Add an initial DNG file writer. The writer can only deal with a small set of pixelformats. The generated file consumable by standard tools. The writer needs to be extended to write more metadata to the generated file. This change also makes libtiff-4 mandatory for qcam. In the future it could be made an optional dependency and only enable DNG support if it's available. Signed-off-by: Niklas Söderlund --- src/qcam/dng_writer.cpp | 152 ++++++++++++++++++++++++++++++++++++++++ src/qcam/dng_writer.h | 32 +++++++++ src/qcam/meson.build | 9 ++- 3 files changed, 190 insertions(+), 3 deletions(-) create mode 100644 src/qcam/dng_writer.cpp create mode 100644 src/qcam/dng_writer.h diff --git a/src/qcam/dng_writer.cpp b/src/qcam/dng_writer.cpp new file mode 100644 index 0000000000000000..b58a6b9938992ed0 --- /dev/null +++ b/src/qcam/dng_writer.cpp @@ -0,0 +1,152 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020, Raspberry Pi (Trading) Ltd. + * + * dng_writer.cpp - DNG writer + */ + +#include "dng_writer.h" + +#include +#include +#include +#include + +#include + +using namespace libcamera; + +struct FormatInfo { + uint8_t bitsPerSample; + uint8_t pattern[4]; /* R = 0, G = 1, B = 2 */ + void (*packScanline)(void *, void *, unsigned int); +}; + +void packScanlineSBGGR10P(void *output, void *input, unsigned int width) +{ + uint8_t *in = static_cast(input); + uint8_t *out = static_cast(output); + + /* \todo: Can this be made more efficient? */ + for (unsigned int i = 0; i < width; i += 4) { + *out++ = in[0]; + *out++ = (in[4] & 0x03) << 6 | in[1] >> 2; + *out++ = (in[1] & 0x03) << 6 | (in[4] & 0x0c) << 2 | in[2] >> 4; + *out++ = (in[2] & 0x0f) << 4 | (in[4] & 0x30) >> 2 | in[3] >> 6; + *out++ = (in[3] & 0x3f) << 2 | (in[4] & 0xc0) >> 6; + in += 5; + } +} + +static const std::map formatInfo = { + { PixelFormat(DRM_FORMAT_SBGGR10, MIPI_FORMAT_MOD_CSI2_PACKED), { 10, { 2, 1, 1, 0 }, packScanlineSBGGR10P } }, + { PixelFormat(DRM_FORMAT_SGBRG10, MIPI_FORMAT_MOD_CSI2_PACKED), { 10, { 1, 2, 0, 1 }, packScanlineSBGGR10P } }, + { PixelFormat(DRM_FORMAT_SGRBG10, MIPI_FORMAT_MOD_CSI2_PACKED), { 10, { 1, 0, 2, 1 }, packScanlineSBGGR10P } }, + { PixelFormat(DRM_FORMAT_SRGGB10, MIPI_FORMAT_MOD_CSI2_PACKED), { 10, { 0, 1, 1, 2 }, packScanlineSBGGR10P } }, +}; + +const FormatInfo *getFormatInfo(const PixelFormat &format) +{ + const auto it = formatInfo.find(format); + if (it == formatInfo.cend()) + return nullptr; + + return &it->second; + + return nullptr; +} + +DNGWriter::DNGWriter(const std::string &pattern) + : pattern_(pattern) +{ +} + +int DNGWriter::write(const Camera *camera, const Stream *stream, + FrameBuffer *buffer, void *data) +{ + const StreamConfiguration &config = stream->configuration(); + + const FormatInfo *info = getFormatInfo(config.pixelFormat); + if (!info) { + std::cerr << "Unsupported pixel format" << std::endl; + return -EINVAL; + } + + uint8_t *scanline = new uint8_t[config.size.width * info->bitsPerSample]; + if (!scanline) { + std::cerr << "Can't allocate memory for scanline" << std::endl; + return -ENOMEM; + } + + std::string filename = genFilename(buffer->metadata()); + + TIFF *tif = TIFFOpen(filename.c_str(), "w"); + if (!tif) { + std::cerr << "Failed to open tiff file" << std::endl; + delete[] scanline; + return -EINVAL; + } + + const uint8_t version[] = "\01\02\00\00"; + 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_SAMPLEFORMAT, SAMPLEFORMAT_UINT); + TIFFSetField(tif, TIFFTAG_CFAREPEATPATTERNDIM, cfa_repeatpatterndim); + TIFFSetField(tif, TIFFTAG_CFAPATTERN, info->pattern); + TIFFSetField(tif, TIFFTAG_CFAPLANECOLOR, 3, "\00\01\02"); + TIFFSetField(tif, TIFFTAG_CFALAYOUT, 1); + + /* \todo: Add more EXIF fields to output. */ + + /* Write RAW content */ + for (unsigned int row = 0; row < config.size.height; row++) { + uint8_t *start = + static_cast(data) + config.stride * row; + + info->packScanline(scanline, start, config.size.width); + + if (TIFFWriteScanline(tif, scanline, row, 0) != 1) { + std::cerr << "Failed to write scanline" << std::endl; + TIFFClose(tif); + delete[] scanline; + return -EINVAL; + } + } + + TIFFWriteDirectory(tif); + + TIFFClose(tif); + + delete[] scanline; + + return 0; +} + +std::string DNGWriter::genFilename(const FrameMetadata &metadata) +{ + std::string filename = pattern_; + + size_t pos = filename.find_first_of('#'); + if (pos != std::string::npos) { + std::stringstream ss; + ss << std::setw(6) << std::setfill('0') << metadata.sequence; + filename.replace(pos, 1, ss.str()); + } + + return filename; +} diff --git a/src/qcam/dng_writer.h b/src/qcam/dng_writer.h new file mode 100644 index 0000000000000000..c7ccbffd5db69dbe --- /dev/null +++ b/src/qcam/dng_writer.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020, Raspberry Pi (Trading) Ltd. + * + * dng_writer.h - DNG writer + */ +#ifndef __LIBCAMERA_DNG_WRITER_H__ +#define __LIBCAMERA_DNG_WRITER_H__ + +#include + +#include +#include +#include + +using namespace libcamera; + +class DNGWriter +{ +public: + DNGWriter(const std::string &pattern = "frame-#.dng"); + + int write(const Camera *camera, const Stream *stream, + FrameBuffer *buffer, void *data); + +private: + std::string genFilename(const FrameMetadata &metadata); + + std::string pattern_; +}; + +#endif /* __LIBCAMERA_DNG_WRITER_H__ */ diff --git a/src/qcam/meson.build b/src/qcam/meson.build index 895264be4a3388f4..8b9a35904facbce0 100644 --- a/src/qcam/meson.build +++ b/src/qcam/meson.build @@ -1,9 +1,10 @@ qcam_sources = files([ + '../cam/options.cpp', + '../cam/stream_options.cpp', + 'dng_writer.cpp', 'format_converter.cpp', 'main.cpp', 'main_window.cpp', - '../cam/options.cpp', - '../cam/stream_options.cpp', 'viewfinder.cpp', ]) @@ -23,6 +24,8 @@ qt5_dep = dependency('qt5', required : false) if qt5_dep.found() + tiff_dep = dependency('libtiff-4', required : true) + qt5_cpp_args = [ '-DQT_NO_KEYWORDS' ] # gcc 9 introduced a deprecated-copy warning that is triggered by Qt until @@ -40,6 +43,6 @@ if qt5_dep.found() qcam = executable('qcam', qcam_sources, resources, install : true, - dependencies : [libcamera_dep, qt5_dep], + dependencies : [libcamera_dep, qt5_dep, tiff_dep], cpp_args : qt5_cpp_args) endif