summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurent Pinchart <laurent.pinchart@ideasonboard.com>2020-05-02 20:31:57 +0300
committerLaurent Pinchart <laurent.pinchart@ideasonboard.com>2020-05-03 04:32:48 +0300
commitd1561e3c36ead5591f65e2eba8ce3bf5ce584fca (patch)
treee08bef273591d35b2f0a6b7c88b0b19a371c0642
parentb1f9b9a9275e5d378c9f9ba8922038c63841f0ec (diff)
qcam: dng_writer: Output thumbnail
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 <laurent.pinchart@ideasonboard.com> Reviewed-by: Niklas Söderlund <niklas.soderlund@ragnatech.se>
-rw-r--r--src/qcam/dng_writer.cpp120
1 files changed, 108 insertions, 12 deletions
diff --git a/src/qcam/dng_writer.cpp b/src/qcam/dng_writer.cpp
index d5270323..e55985ba 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<const uint8_t *>(input);
+ uint8_t *out = static_cast<uint8_t *>(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<PixelFormat, FormatInfo> 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_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_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);
+
+ /*
+ * 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<const uint8_t *>(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_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_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<const uint8_t *>(data);
- uint8_t scanline[(config.size.width * info->bitsPerSample + 7) / 8];
+ row = static_cast<const uint8_t *>(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);