Show a patch.

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

{
    "id": 19798,
    "url": "https://patchwork.libcamera.org/api/patches/19798/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/19798/",
    "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": "<20240322195032.1803017-2-mzamazal@redhat.com>",
    "date": "2024-03-22T19:50:32",
    "name": "[v4,1/1] apps: cam: Add support for PPM output format",
    "commit_ref": null,
    "pull_url": null,
    "state": "accepted",
    "archived": false,
    "hash": "dc35034299e0ca896c4f6969f1d54e9ae6f35198",
    "submitter": {
        "id": 177,
        "url": "https://patchwork.libcamera.org/api/people/177/?format=api",
        "name": "Milan Zamazal",
        "email": "mzamazal@redhat.com"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/19798/mbox/",
    "series": [
        {
            "id": 4237,
            "url": "https://patchwork.libcamera.org/api/series/4237/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=4237",
            "date": "2024-03-22T19:50:31",
            "name": "cam: Add support for PPM output format",
            "version": 4,
            "mbox": "https://patchwork.libcamera.org/series/4237/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/19798/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/19798/checks/",
    "tags": {},
    "headers": {
        "Return-Path": "<libcamera-devel-bounces@lists.libcamera.org>",
        "X-Original-To": "parsemail@patchwork.libcamera.org",
        "Delivered-To": "parsemail@patchwork.libcamera.org",
        "Received": [
            "from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id 2AA70BD160\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 22 Mar 2024 19:51:09 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id D6E1263075;\n\tFri, 22 Mar 2024 20:51:08 +0100 (CET)",
            "from us-smtp-delivery-124.mimecast.com\n\t(us-smtp-delivery-124.mimecast.com [170.10.129.124])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id C47DC61C45\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 22 Mar 2024 20:51:06 +0100 (CET)",
            "from mimecast-mx02.redhat.com (mx-ext.redhat.com [66.187.233.73])\n\tby relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3,\n\tcipher=TLS_AES_256_GCM_SHA384) id us-mta-15-EasMW90qOoGwXm2iE0_tyA-1;\n\tFri, 22 Mar 2024 15:51:02 -0400",
            "from smtp.corp.redhat.com\n\t(int-mx04.intmail.prod.int.rdu2.redhat.com [10.11.54.4])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\tkey-exchange X25519 server-signature RSA-PSS (2048 bits)\n\tserver-digest SHA256) (No client certificate requested)\n\tby mimecast-mx02.redhat.com (Postfix) with ESMTPS id D25CA3C02455;\n\tFri, 22 Mar 2024 19:51:01 +0000 (UTC)",
            "from nuthatch.redhat.com (unknown [10.45.224.17])\n\tby smtp.corp.redhat.com (Postfix) with ESMTP id AF7842022C1D;\n\tFri, 22 Mar 2024 19:51:00 +0000 (UTC)"
        ],
        "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=redhat.com header.i=@redhat.com\n\theader.b=\"f+22tYOy\"; dkim-atps=neutral",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n\ts=mimecast20190719; t=1711137065;\n\th=from:from:reply-to:subject:subject:date:date:message-id:message-id:\n\tto:to:cc:cc:mime-version:mime-version:content-type:content-type:\n\tcontent-transfer-encoding:content-transfer-encoding:\n\tin-reply-to:in-reply-to:references:references;\n\tbh=d07stczum5vCHDq24XkUO+35VPXkSpsiBsWhPKPBfec=;\n\tb=f+22tYOymmyCdUEayO1LhNHPMLYG71xpihNc9E2zsDUJZAgpsCyh7D36SzGRaxnGlZCiIY\n\t6VLy2wBObq0RddzP290DCbuQRZI7CM4sbV16j5DdYCdlcEL2n7N9udQt6jItEWeU3ppFIX\n\ta2oqW2ns6gvrG0A1b+Wq1N5U4O8z8kY=",
        "X-MC-Unique": "EasMW90qOoGwXm2iE0_tyA-1",
        "From": "Milan Zamazal <mzamazal@redhat.com>",
        "To": "libcamera-devel@lists.libcamera.org",
        "Cc": "Milan Zamazal <mzamazal@redhat.com>,\n\tKieran Bingham <kieran.bingham@ideasonboard.com>,\n\tLaurent Pinchart <laurent.pinchart@ideasonboard.com>",
        "Subject": "[PATCH v4 1/1] apps: cam: Add support for PPM output format",
        "Date": "Fri, 22 Mar 2024 20:50:32 +0100",
        "Message-ID": "<20240322195032.1803017-2-mzamazal@redhat.com>",
        "In-Reply-To": "<20240322195032.1803017-1-mzamazal@redhat.com>",
        "References": "<20240322195032.1803017-1-mzamazal@redhat.com>",
        "MIME-Version": "1.0",
        "X-Scanned-By": "MIMEDefang 3.4.1 on 10.11.54.4",
        "X-Mimecast-Spam-Score": "0",
        "X-Mimecast-Originator": "redhat.com",
        "Content-Transfer-Encoding": "8bit",
        "Content-Type": "text/plain; charset=\"US-ASCII\"; x-default=true",
        "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>",
        "Errors-To": "libcamera-devel-bounces@lists.libcamera.org",
        "Sender": "\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"
    },
    "content": "When file output is requested from cam app, it simply dumps the processed data\nand it must be converted to a readable image format manually.  Let's add support\nfor PPM output file format to make files produced by cam directly readable by\nimage display and processing software.\n\nFor now, only BGR888 output format, which is the simplest one to use, is\nsupported but nothing prevents adding support for other output formats if\nneeded.  Nevertheless, they would typically need byte reordering or other\nconversions for PPM file format.  It may be better to find a way to dump the\nimage data in other output formats directly using some of the already existing\nfile formats or raw file format converters.\n\nSigned-off-by: Milan Zamazal <mzamazal@redhat.com>\nReviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n---\n src/apps/cam/file_sink.cpp     | 11 +++++++\n src/apps/cam/main.cpp          |  2 ++\n src/apps/common/meson.build    |  1 +\n src/apps/common/ppm_writer.cpp | 53 ++++++++++++++++++++++++++++++++++\n src/apps/common/ppm_writer.h   | 20 +++++++++++++\n 5 files changed, 87 insertions(+)\n create mode 100644 src/apps/common/ppm_writer.cpp\n create mode 100644 src/apps/common/ppm_writer.h",
    "diff": "diff --git a/src/apps/cam/file_sink.cpp b/src/apps/cam/file_sink.cpp\nindex dca350c4..906b50e6 100644\n--- a/src/apps/cam/file_sink.cpp\n+++ b/src/apps/cam/file_sink.cpp\n@@ -17,6 +17,7 @@\n \n #include \"../common/dng_writer.h\"\n #include \"../common/image.h\"\n+#include \"../common/ppm_writer.h\"\n \n #include \"file_sink.h\"\n \n@@ -76,6 +77,7 @@ void FileSink::writeBuffer(const Stream *stream, FrameBuffer *buffer,\n #ifdef HAVE_TIFF\n \tbool dng = filename.find(\".dng\", filename.size() - 4) != std::string::npos;\n #endif /* HAVE_TIFF */\n+\tbool ppm = filename.find(\".ppm\", filename.size() - 4) != std::string::npos;\n \n \tif (filename.empty() || filename.back() == '/')\n \t\tfilename += \"frame-#.bin\";\n@@ -102,6 +104,15 @@ void FileSink::writeBuffer(const Stream *stream, FrameBuffer *buffer,\n \t\treturn;\n \t}\n #endif /* HAVE_TIFF */\n+\tif (ppm) {\n+\t\tret = PPMWriter::write(filename.c_str(), stream->configuration(),\n+\t\t\t\t       image->data(0));\n+\t\tif (ret < 0)\n+\t\t\tstd::cerr << \"failed to write PPM file `\" << filename\n+\t\t\t\t  << \"'\" << std::endl;\n+\n+\t\treturn;\n+\t}\n \n \tfd = open(filename.c_str(), O_CREAT | O_WRONLY |\n \t\t  (pos == std::string::npos ? O_APPEND : O_TRUNC),\ndiff --git a/src/apps/cam/main.cpp b/src/apps/cam/main.cpp\nindex 179cc376..1aabee01 100644\n--- a/src/apps/cam/main.cpp\n+++ b/src/apps/cam/main.cpp\n@@ -154,6 +154,8 @@ int CamApp::parseOptions(int argc, char *argv[])\n \t\t\t \"If the file name ends with '.dng', then the frame will be written to\\n\"\n \t\t\t \"the output file(s) in DNG format.\\n\"\n #endif\n+\t\t\t \"If the file name ends with '.ppm', then the frame will be written to\\n\"\n+\t\t\t \"the output file(s) in PPM format.\\n\"\n \t\t\t \"The default file name is 'frame-#.bin'.\",\n \t\t\t \"file\", ArgumentOptional, \"filename\", false,\n \t\t\t OptCamera);\ndiff --git a/src/apps/common/meson.build b/src/apps/common/meson.build\nindex 479326cd..5b683390 100644\n--- a/src/apps/common/meson.build\n+++ b/src/apps/common/meson.build\n@@ -3,6 +3,7 @@\n apps_sources = files([\n     'image.cpp',\n     'options.cpp',\n+    'ppm_writer.cpp',\n     'stream_options.cpp',\n ])\n \ndiff --git a/src/apps/common/ppm_writer.cpp b/src/apps/common/ppm_writer.cpp\nnew file mode 100644\nindex 00000000..04a49499\n--- /dev/null\n+++ b/src/apps/common/ppm_writer.cpp\n@@ -0,0 +1,53 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2024 Red Hat, Inc.\n+ *\n+ * ppm_writer.cpp - PPM writer\n+ */\n+\n+#include <fstream>\n+#include <iostream>\n+\n+#include <libcamera/formats.h>\n+#include <libcamera/pixel_format.h>\n+\n+#include \"ppm_writer.h\"\n+\n+using namespace libcamera;\n+\n+int PPMWriter::write(const char *filename,\n+\t\t     const StreamConfiguration &config,\n+\t\t     const Span<uint8_t> &data)\n+{\n+\tif (config.pixelFormat != formats::BGR888) {\n+\t\tstd::cerr << \"Only BGR888 output pixel format is supported (\"\n+\t\t\t  << config.pixelFormat << \" requested)\" << std::endl;\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tstd::ofstream output(filename, std::ios::binary);\n+\tif (!output) {\n+\t\tstd::cerr << \"Failed to open ppm file: \" << filename << std::endl;\n+\t\treturn -EINVAL;\n+\t}\n+\n+\toutput << \"P6\" << std::endl\n+\t       << config.size.width << \" \" << config.size.height << std::endl\n+\t       << \"255\" << std::endl;\n+\tif (!output) {\n+\t\tstd::cerr << \"Failed to write the file header\" << std::endl;\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tconst unsigned int rowLength = config.size.width * 3;\n+\tconst char *row = reinterpret_cast<const char *>(data.data());\n+\tfor (unsigned int y = 0; y < config.size.height; y++, row += config.stride) {\n+\t\toutput.write(row, rowLength);\n+\t\tif (!output) {\n+\t\t\tstd::cerr << \"Failed to write image data at row \" << y << std::endl;\n+\t\t\treturn -EINVAL;\n+\t\t}\n+\t}\n+\n+\treturn 0;\n+}\ndiff --git a/src/apps/common/ppm_writer.h b/src/apps/common/ppm_writer.h\nnew file mode 100644\nindex 00000000..4c38f5ce\n--- /dev/null\n+++ b/src/apps/common/ppm_writer.h\n@@ -0,0 +1,20 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2024, Red Hat, Inc.\n+ *\n+ * ppm_writer.h - PPM writer\n+ */\n+\n+#pragma once\n+\n+#include <libcamera/base/span.h>\n+\n+#include <libcamera/stream.h>\n+\n+class PPMWriter\n+{\n+public:\n+\tstatic int write(const char *filename,\n+\t\t\t const libcamera::StreamConfiguration &config,\n+\t\t\t const libcamera::Span<uint8_t> &data);\n+};\n",
    "prefixes": [
        "v4",
        "1/1"
    ]
}