Show a patch.

GET /api/patches/3674/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 3674,
    "url": "https://patchwork.libcamera.org/api/patches/3674/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/3674/",
    "project": {
        "id": 1,
        "url": "https://patchwork.libcamera.org/api/projects/1/?format=api",
        "name": "libcamera",
        "link_name": "libcamera",
        "list_id": "libcamera_core",
        "list_email": "libcamera-devel@lists.libcamera.org",
        "web_url": "",
        "scm_url": "",
        "webscm_url": ""
    },
    "msgid": "<20200502225704.2911-4-laurent.pinchart@ideasonboard.com>",
    "date": "2020-05-02T22:57:02",
    "name": "[libcamera-devel,3/5] qcam: dng_writer: Output thumbnail",
    "commit_ref": null,
    "pull_url": null,
    "state": "accepted",
    "archived": false,
    "hash": "2ba2d41fb7e088b5fa8eac5e0f50a40aa3199ea1",
    "submitter": {
        "id": 2,
        "url": "https://patchwork.libcamera.org/api/people/2/?format=api",
        "name": "Laurent Pinchart",
        "email": "laurent.pinchart@ideasonboard.com"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/3674/mbox/",
    "series": [
        {
            "id": 874,
            "url": "https://patchwork.libcamera.org/api/series/874/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=874",
            "date": "2020-05-02T22:56:59",
            "name": "qcam: Improve DNG writer",
            "version": 1,
            "mbox": "https://patchwork.libcamera.org/series/874/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/3674/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/3674/checks/",
    "tags": {},
    "headers": {
        "Return-Path": "<laurent.pinchart@ideasonboard.com>",
        "Received": [
            "from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id BC61E6162E\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tSun,  3 May 2020 00:57:11 +0200 (CEST)",
            "from pendragon.bb.dnainternet.fi (81-175-216-236.bb.dnainternet.fi\n\t[81.175.216.236])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 582FB72C\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tSun,  3 May 2020 00:57:11 +0200 (CEST)"
        ],
        "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=ideasonboard.com\n\theader.i=@ideasonboard.com\n\theader.b=\"gy7TcazE\"; dkim-atps=neutral",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1588460231;\n\tbh=xnEC6zKrNN2kCkjSrIEpDNfHBLCq+Yw9i379p1zjxU4=;\n\th=From:To:Subject:Date:In-Reply-To:References:From;\n\tb=gy7TcazEdPij1YVGdRwy3hfJoL1ZQzRSICg0iaW2LZxcFHMOWHBHTnuHj2HmZwR8c\n\tZtagjA6bI+0ocd7ieH+keHP2cSuZPxQ0O1F/UfGj1D+4SP5vXu/ZwmkfMUIPAEybqv\n\tsrSTgtNKRpTVwHfFoG9WVe2mdfgZsZG9yzeoxBT0=",
        "From": "Laurent Pinchart <laurent.pinchart@ideasonboard.com>",
        "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",
        "Content-Transfer-Encoding": "8bit",
        "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": "<libcamera-devel.lists.libcamera.org>",
        "List-Unsubscribe": "<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>",
        "List-Archive": "<https://lists.libcamera.org/pipermail/libcamera-devel/>",
        "List-Post": "<mailto:libcamera-devel@lists.libcamera.org>",
        "List-Help": "<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>",
        "List-Subscribe": "<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>",
        "X-List-Received-Date": "Sat, 02 May 2020 22:57:12 -0000"
    },
    "content": "Generate a greyscale, 1/16 resolution thumbnail and add it to the DNG\nfile. This requires shuffling the RAW image generation as the thumbnail\nhas to be stored in the main IFD as per the DNG and TIFF/EP\nspecifications.\n\nSigned-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n---\n src/qcam/dng_writer.cpp | 126 +++++++++++++++++++++++++++++++++++-----\n 1 file changed, 111 insertions(+), 15 deletions(-)",
    "diff": "diff --git a/src/qcam/dng_writer.cpp b/src/qcam/dng_writer.cpp\nindex d5270323abd4..e55985ba86e7 100644\n--- a/src/qcam/dng_writer.cpp\n+++ b/src/qcam/dng_writer.cpp\n@@ -25,6 +25,9 @@ struct FormatInfo {\n \tCFAPatternColour pattern[4];\n \tvoid (*packScanline)(void *output, const void *input,\n \t\t\t     unsigned int width);\n+\tvoid (*thumbScanline)(const FormatInfo &info, void *output,\n+\t\t\t      const void *input, unsigned int width,\n+\t\t\t      unsigned int stride);\n };\n \n void packScanlineSBGGR10P(void *output, const void *input, unsigned int width)\n@@ -57,46 +60,70 @@ void packScanlineSBGGR12P(void *output, const void *input, unsigned int width)\n \t}\n }\n \n+void thumbScanlineSBGGRxxP(const FormatInfo &info, void *output,\n+\t\t\t   const void *input, unsigned int width,\n+\t\t\t   unsigned int stride)\n+{\n+\tconst uint8_t *in = static_cast<const uint8_t *>(input);\n+\tuint8_t *out = static_cast<uint8_t *>(output);\n+\n+\t/* Number of bytes corresponding to 16 pixels. */\n+\tunsigned int skip = info.bitsPerSample * 16 / 8;\n+\n+\tfor (unsigned int x = 0; x < width; x++) {\n+\t\t*out++ = (in[0] + in[1] + in[stride] + in[stride + 1]) >> 2;\n+\t\tin += skip;\n+\t}\n+}\n+\n static const std::map<PixelFormat, FormatInfo> formatInfo = {\n \t{ PixelFormat(DRM_FORMAT_SBGGR10, MIPI_FORMAT_MOD_CSI2_PACKED), {\n \t\t.bitsPerSample = 10,\n \t\t.pattern = { CFAPatternBlue, CFAPatternGreen, CFAPatternGreen, CFAPatternRed },\n \t\t.packScanline = packScanlineSBGGR10P,\n+\t\t.thumbScanline = thumbScanlineSBGGRxxP,\n \t} },\n \t{ PixelFormat(DRM_FORMAT_SGBRG10, MIPI_FORMAT_MOD_CSI2_PACKED), {\n \t\t.bitsPerSample = 10,\n \t\t.pattern = { CFAPatternGreen, CFAPatternBlue, CFAPatternRed, CFAPatternGreen },\n \t\t.packScanline = packScanlineSBGGR10P,\n+\t\t.thumbScanline = thumbScanlineSBGGRxxP,\n \t} },\n \t{ PixelFormat(DRM_FORMAT_SGRBG10, MIPI_FORMAT_MOD_CSI2_PACKED), {\n \t\t.bitsPerSample = 10,\n \t\t.pattern = { CFAPatternGreen, CFAPatternRed, CFAPatternBlue, CFAPatternGreen },\n \t\t.packScanline = packScanlineSBGGR10P,\n+\t\t.thumbScanline = thumbScanlineSBGGRxxP,\n \t} },\n \t{ PixelFormat(DRM_FORMAT_SRGGB10, MIPI_FORMAT_MOD_CSI2_PACKED), {\n \t\t.bitsPerSample = 10,\n \t\t.pattern = { CFAPatternRed, CFAPatternGreen, CFAPatternGreen, CFAPatternBlue },\n \t\t.packScanline = packScanlineSBGGR10P,\n+\t\t.thumbScanline = thumbScanlineSBGGRxxP,\n \t} },\n \t{ PixelFormat(DRM_FORMAT_SBGGR12, MIPI_FORMAT_MOD_CSI2_PACKED), {\n \t\t.bitsPerSample = 12,\n \t\t.pattern = { CFAPatternBlue, CFAPatternGreen, CFAPatternGreen, CFAPatternRed },\n \t\t.packScanline = packScanlineSBGGR12P,\n+\t\t.thumbScanline = thumbScanlineSBGGRxxP,\n \t} },\n \t{ PixelFormat(DRM_FORMAT_SGBRG12, MIPI_FORMAT_MOD_CSI2_PACKED), {\n \t\t.bitsPerSample = 12,\n \t\t.pattern = { CFAPatternGreen, CFAPatternBlue, CFAPatternRed, CFAPatternGreen },\n \t\t.packScanline = packScanlineSBGGR12P,\n+\t\t.thumbScanline = thumbScanlineSBGGRxxP,\n \t} },\n \t{ PixelFormat(DRM_FORMAT_SGRBG12, MIPI_FORMAT_MOD_CSI2_PACKED), {\n \t\t.bitsPerSample = 12,\n \t\t.pattern = { CFAPatternGreen, CFAPatternRed, CFAPatternBlue, CFAPatternGreen },\n \t\t.packScanline = packScanlineSBGGR12P,\n+\t\t.thumbScanline = thumbScanlineSBGGRxxP,\n \t} },\n \t{ PixelFormat(DRM_FORMAT_SRGGB12, MIPI_FORMAT_MOD_CSI2_PACKED), {\n \t\t.bitsPerSample = 12,\n \t\t.pattern = { CFAPatternRed, CFAPatternGreen, CFAPatternGreen, CFAPatternBlue },\n \t\t.packScanline = packScanlineSBGGR12P,\n+\t\t.thumbScanline = thumbScanlineSBGGRxxP,\n \t} },\n };\n \n@@ -118,40 +145,101 @@ int DNGWriter::write(const char *filename, const Camera *camera,\n \t\treturn -EINVAL;\n \t}\n \n+\t/*\n+\t * Scanline buffer, has to be large enough to store both a RAW scanline\n+\t * or a thumbnail scanline. The latter will always be much smaller than\n+\t * the former as we downscale by 16 in both directions.\n+\t */\n+\tuint8_t scanline[(config.size.width * info->bitsPerSample + 7) / 8];\n+\n+\ttoff_t rawIFDOffset = 0;\n+\n+\t/*\n+\t * Start with a thumbnail in IFD 0 for compatibility with TIFF baseline\n+\t * readers, as required by the TIFF/EP specification. Tags that apply to\n+\t * the whole file are stored here.\n+\t */\n \tconst uint8_t version[] = { 1, 2, 0, 0 };\n-\tconst uint16_t cfa_repeatpatterndim[] = { 2, 2 };\n+\n \tTIFFSetField(tif, TIFFTAG_DNGVERSION, version);\n \tTIFFSetField(tif, TIFFTAG_DNGBACKWARDVERSION, version);\n-\tTIFFSetField(tif, TIFFTAG_SUBFILETYPE, 0);\n-\tTIFFSetField(tif, TIFFTAG_IMAGEWIDTH, config.size.width);\n-\tTIFFSetField(tif, TIFFTAG_IMAGELENGTH, config.size.height);\n-\tTIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, info->bitsPerSample);\n-\tTIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE);\n-\tTIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_CFA);\n \tTIFFSetField(tif, TIFFTAG_FILLORDER, FILLORDER_MSB2LSB);\n \tTIFFSetField(tif, TIFFTAG_MAKE, \"libcamera\");\n \tTIFFSetField(tif, TIFFTAG_MODEL, camera->name().c_str());\n \tTIFFSetField(tif, TIFFTAG_UNIQUECAMERAMODEL, camera->name().c_str());\n-\tTIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);\n-\tTIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1);\n-\tTIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);\n \tTIFFSetField(tif, TIFFTAG_SOFTWARE, \"qcam\");\n+\tTIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);\n+\n+\t/*\n+\t * Thumbnail-specific tags. The thumbnail is stored as a greyscale image\n+\t * with 1/16 of the raw image resolution.\n+\t */\n+\tTIFFSetField(tif, TIFFTAG_SUBFILETYPE, FILETYPE_REDUCEDIMAGE);\n+\tTIFFSetField(tif, TIFFTAG_IMAGEWIDTH, config.size.width / 16);\n+\tTIFFSetField(tif, TIFFTAG_IMAGELENGTH, config.size.height / 16);\n+\tTIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8);\n+\tTIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE);\n+\tTIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK);\n+\tTIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1);\n+\tTIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);\n \tTIFFSetField(tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT);\n-\tTIFFSetField(tif, TIFFTAG_CFAREPEATPATTERNDIM, cfa_repeatpatterndim);\n+\n+\t/*\n+\t * Reserve space for the SubIFD tag, pointing to the IFD for the raw\n+\t * image. The real offset will be set later.\n+\t */\n+\tTIFFSetField(tif, TIFFTAG_SUBIFD, 1, &rawIFDOffset);\n+\n+\t/* Write the thumbnail. */\n+\tconst uint8_t *row = static_cast<const uint8_t *>(data);\n+\tfor (unsigned int y = 0; y < config.size.height / 16; y++) {\n+\t\tinfo->thumbScanline(*info, &scanline, row,\n+\t\t\t\t    config.size.width / 16, config.stride);\n+\n+\t\tif (TIFFWriteScanline(tif, &scanline, y, 0) != 1) {\n+\t\t\tstd::cerr << \"Failed to write thumbnail scanline\"\n+\t\t\t\t  << std::endl;\n+\t\t\tTIFFClose(tif);\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\n+\t\trow += config.stride * 16;\n+\t}\n+\n+\tTIFFWriteDirectory(tif);\n+\n+\t/* Create a new IFD for the RAW image. */\n+\tconst uint16_t cfaRepeatPatternDim[] = { 2, 2 };\n+\tconst uint8_t cfaPlaneColor[] = {\n+\t\tCFAPatternRed,\n+\t\tCFAPatternGreen,\n+\t\tCFAPatternBlue\n+\t};\n+\n+\tTIFFSetField(tif, TIFFTAG_SUBFILETYPE, 0);\n+\tTIFFSetField(tif, TIFFTAG_IMAGEWIDTH, config.size.width);\n+\tTIFFSetField(tif, TIFFTAG_IMAGELENGTH, config.size.height);\n+\tTIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, info->bitsPerSample);\n+\tTIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE);\n+\tTIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_CFA);\n+\tTIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1);\n+\tTIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);\n+\tTIFFSetField(tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT);\n+\tTIFFSetField(tif, TIFFTAG_CFAREPEATPATTERNDIM, cfaRepeatPatternDim);\n \tTIFFSetField(tif, TIFFTAG_CFAPATTERN, info->pattern);\n-\tTIFFSetField(tif, TIFFTAG_CFAPLANECOLOR, 3, \"\\00\\01\\02\");\n+\tTIFFSetField(tif, TIFFTAG_CFAPLANECOLOR, 3, cfaPlaneColor);\n \tTIFFSetField(tif, TIFFTAG_CFALAYOUT, 1);\n \n \t/* \\todo Add more EXIF fields to output. */\n \n \t/* Write RAW content. */\n-\tconst uint8_t *row = static_cast<const uint8_t *>(data);\n-\tuint8_t scanline[(config.size.width * info->bitsPerSample + 7) / 8];\n+\trow = static_cast<const uint8_t *>(data);\n \tfor (unsigned int y = 0; y < config.size.height; y++) {\n \t\tinfo->packScanline(&scanline, row, config.size.width);\n \n \t\tif (TIFFWriteScanline(tif, &scanline, y, 0) != 1) {\n-\t\t\tstd::cerr << \"Failed to write scanline\" << std::endl;\n+\t\t\tstd::cerr << \"Failed to write RAW scanline\"\n+\t\t\t\t  << std::endl;\n \t\t\tTIFFClose(tif);\n \t\t\treturn -EINVAL;\n \t\t}\n@@ -159,6 +247,14 @@ int DNGWriter::write(const char *filename, const Camera *camera,\n \t\trow += config.stride;\n \t}\n \n+\t/* Checkpoint the IFD to retrieve its offset, and write it out. */\n+\tTIFFCheckpointDirectory(tif);\n+\trawIFDOffset = TIFFCurrentDirOffset(tif);\n+\tTIFFWriteDirectory(tif);\n+\n+\t/* Update the IFD offsets and close the file. */\n+\tTIFFSetDirectory(tif, 0);\n+\tTIFFSetField(tif, TIFFTAG_SUBIFD, 1, &rawIFDOffset);\n \tTIFFWriteDirectory(tif);\n \n \tTIFFClose(tif);\n",
    "prefixes": [
        "libcamera-devel",
        "3/5"
    ]
}