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);