[{"id":29117,"web_url":"https://patchwork.libcamera.org/comment/29117/","msgid":"<20240328010623.GD21080@pendragon.ideasonboard.com>","date":"2024-03-28T01:06:23","subject":"Re: [PATCH v4 1/1] apps: cam: Add support for PPM output format","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi Milan,\n\nThank you for the patch.\n\nOn Fri, Mar 22, 2024 at 08:50:32PM +0100, Milan Zamazal wrote:\n> When file output is requested from cam app, it simply dumps the processed data\n> and it must be converted to a readable image format manually.  Let's add support\n> for PPM output file format to make files produced by cam directly readable by\n> image display and processing software.\n> \n> For now, only BGR888 output format, which is the simplest one to use, is\n> supported but nothing prevents adding support for other output formats if\n> needed.  Nevertheless, they would typically need byte reordering or other\n> conversions for PPM file format.  It may be better to find a way to dump the\n> image data in other output formats directly using some of the already existing\n> file formats or raw file format converters.\n> \n> Signed-off-by: Milan Zamazal <mzamazal@redhat.com>\n> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n\nReviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n\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\n> \n> diff --git a/src/apps/cam/file_sink.cpp b/src/apps/cam/file_sink.cpp\n> index 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),\n> diff --git a/src/apps/cam/main.cpp b/src/apps/cam/main.cpp\n> index 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);\n> diff --git a/src/apps/common/meson.build b/src/apps/common/meson.build\n> index 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>  \n> diff --git a/src/apps/common/ppm_writer.cpp b/src/apps/common/ppm_writer.cpp\n> new file mode 100644\n> index 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> +}\n> diff --git a/src/apps/common/ppm_writer.h b/src/apps/common/ppm_writer.h\n> new file mode 100644\n> index 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> +};","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 45906C0DA4\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 28 Mar 2024 01:06:35 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 4940663331;\n\tThu, 28 Mar 2024 02:06:34 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id BBF0D61C31\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 28 Mar 2024 02:06:31 +0100 (CET)","from pendragon.ideasonboard.com (81-175-209-231.bb.dnainternet.fi\n\t[81.175.209.231])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id C6418541;\n\tThu, 28 Mar 2024 02:05:58 +0100 (CET)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"o8A6STQy\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1711587959;\n\tbh=X6ua/gcDXcyYI2gjVBOzd2aOoxtt2Ihs19ixPlw35f4=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=o8A6STQyuLLgo0WgtK6U9kSUA06epr/NmtKWDbTc/4QociBqQCuoDUh+l+GGrwLrT\n\tNoiCDHBaaGwpz82LZz+rjRGDczk7cth2edf9bu8mu5j8iBJm4OSMaV4Kgtyfr7wA8j\n\tJUJA5Phm9ICMTRQOskuRLlmXxtNmZneihqQU5x/Q=","Date":"Thu, 28 Mar 2024 03:06:23 +0200","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"Milan Zamazal <mzamazal@redhat.com>","Cc":"libcamera-devel@lists.libcamera.org,\n\tKieran Bingham <kieran.bingham@ideasonboard.com>","Subject":"Re: [PATCH v4 1/1] apps: cam: Add support for PPM output format","Message-ID":"<20240328010623.GD21080@pendragon.ideasonboard.com>","References":"<20240322195032.1803017-1-mzamazal@redhat.com>\n\t<20240322195032.1803017-2-mzamazal@redhat.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20240322195032.1803017-2-mzamazal@redhat.com>","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>"}},{"id":29118,"web_url":"https://patchwork.libcamera.org/comment/29118/","msgid":"<20240328011153.GE21080@pendragon.ideasonboard.com>","date":"2024-03-28T01:11:53","subject":"Re: [PATCH v4 1/1] apps: cam: Add support for PPM output format","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Actually,\n\nOn Thu, Mar 28, 2024 at 03:06:23AM +0200, Laurent Pinchart wrote:\n> On Fri, Mar 22, 2024 at 08:50:32PM +0100, Milan Zamazal wrote:\n> > When file output is requested from cam app, it simply dumps the processed data\n> > and it must be converted to a readable image format manually.  Let's add support\n> > for PPM output file format to make files produced by cam directly readable by\n> > image display and processing software.\n> > \n> > For now, only BGR888 output format, which is the simplest one to use, is\n> > supported but nothing prevents adding support for other output formats if\n> > needed.  Nevertheless, they would typically need byte reordering or other\n> > conversions for PPM file format.  It may be better to find a way to dump the\n> > image data in other output formats directly using some of the already existing\n> > file formats or raw file format converters.\n> > \n> > Signed-off-by: Milan Zamazal <mzamazal@redhat.com>\n> > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n> \n> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> \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\n> > \n> > diff --git a/src/apps/cam/file_sink.cpp b/src/apps/cam/file_sink.cpp\n> > index 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),\n> > diff --git a/src/apps/cam/main.cpp b/src/apps/cam/main.cpp\n> > index 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);\n> > diff --git a/src/apps/common/meson.build b/src/apps/common/meson.build\n> > index 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> >  \n> > diff --git a/src/apps/common/ppm_writer.cpp b/src/apps/common/ppm_writer.cpp\n> > new file mode 100644\n> > index 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\ncheckstyle.py told me that this should go to the top. I'll fix when\napplying.\n\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> > +}\n> > diff --git a/src/apps/common/ppm_writer.h b/src/apps/common/ppm_writer.h\n> > new file mode 100644\n> > index 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> > +};","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 A5FEBBD160\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 28 Mar 2024 01:12:04 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id D00076333B;\n\tThu, 28 Mar 2024 02:12:03 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id C148961C31\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 28 Mar 2024 02:12:01 +0100 (CET)","from pendragon.ideasonboard.com (81-175-209-231.bb.dnainternet.fi\n\t[81.175.209.231])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id D83B01A2;\n\tThu, 28 Mar 2024 02:11:28 +0100 (CET)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"AxaxA90t\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1711588289;\n\tbh=NCOLgDIjttRkndsLJHs2nSlAxgiD4G2FH03AFBhi/As=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=AxaxA90tzt/HzVoq1IaI/PMaTkX9sAWETHV7HY/M2mix2NRkeAsDVy9vrPzI2s4qg\n\tQcibjyifNuEuJ0p+kADsV+AoVOqaYAxaCfPG/J1zVWvJs5C6/SIW19RynAfn5W/eiE\n\tP7zuV02lFR62txVwuJtzdhRB/2gx4BiboIyb67oQ=","Date":"Thu, 28 Mar 2024 03:11:53 +0200","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"Milan Zamazal <mzamazal@redhat.com>","Cc":"libcamera-devel@lists.libcamera.org,\n\tKieran Bingham <kieran.bingham@ideasonboard.com>","Subject":"Re: [PATCH v4 1/1] apps: cam: Add support for PPM output format","Message-ID":"<20240328011153.GE21080@pendragon.ideasonboard.com>","References":"<20240322195032.1803017-1-mzamazal@redhat.com>\n\t<20240322195032.1803017-2-mzamazal@redhat.com>\n\t<20240328010623.GD21080@pendragon.ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20240328010623.GD21080@pendragon.ideasonboard.com>","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>"}},{"id":29119,"web_url":"https://patchwork.libcamera.org/comment/29119/","msgid":"<87plveixkt.fsf@redhat.com>","date":"2024-03-28T08:12:50","subject":"Re: [PATCH v4 1/1] apps: cam: Add support for PPM output format","submitter":{"id":177,"url":"https://patchwork.libcamera.org/api/people/177/","name":"Milan Zamazal","email":"mzamazal@redhat.com"},"content":"Laurent Pinchart <laurent.pinchart@ideasonboard.com> writes:\n\n> Actually,\n>\n> On Thu, Mar 28, 2024 at 03:06:23AM +0200, Laurent Pinchart wrote:\n>> On Fri, Mar 22, 2024 at 08:50:32PM +0100, Milan Zamazal wrote:\n>> > When file output is requested from cam app, it simply dumps the processed data\n>> > and it must be converted to a readable image format manually.  Let's add support\n>> > for PPM output file format to make files produced by cam directly readable by\n>> > image display and processing software.\n>> > \n>> > For now, only BGR888 output format, which is the simplest one to use, is\n>> > supported but nothing prevents adding support for other output formats if\n>> > needed.  Nevertheless, they would typically need byte reordering or other\n>> > conversions for PPM file format.  It may be better to find a way to dump the\n>> > image data in other output formats directly using some of the already existing\n>> > file formats or raw file format converters.\n>> > \n>> > Signed-off-by: Milan Zamazal <mzamazal@redhat.com>\n>> > Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n>> \n>> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n>> \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\n>> > \n>> > diff --git a/src/apps/cam/file_sink.cpp b/src/apps/cam/file_sink.cpp\n>> > index 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),\n>> > diff --git a/src/apps/cam/main.cpp b/src/apps/cam/main.cpp\n>> > index 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);\n>> > diff --git a/src/apps/common/meson.build b/src/apps/common/meson.build\n>> > index 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>> >  \n>> > diff --git a/src/apps/common/ppm_writer.cpp b/src/apps/common/ppm_writer.cpp\n>> > new file mode 100644\n>> > index 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> checkstyle.py told me that this should go to the top. I'll fix when\n> applying.\n\nThanks!  (And sorry for this omission.)\n\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>> > +}\n>> > diff --git a/src/apps/common/ppm_writer.h b/src/apps/common/ppm_writer.h\n>> > new file mode 100644\n>> > index 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>> > +};","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 7F042C0DA4\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu, 28 Mar 2024 08:12:59 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 93ABF6332E;\n\tThu, 28 Mar 2024 09:12:58 +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 6788361C39\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 28 Mar 2024 09:12:56 +0100 (CET)","from mail-wm1-f70.google.com (mail-wm1-f70.google.com\n\t[209.85.128.70]) by relay.mimecast.com with ESMTP with STARTTLS\n\t(version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id\n\tus-mta-665-lEZkRGF9O-Sdkl7Q9XPXdA-1; Thu, 28 Mar 2024 04:12:52 -0400","by mail-wm1-f70.google.com with SMTP id\n\t5b1f17b1804b1-4140bf38378so5121335e9.1\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 28 Mar 2024 01:12:52 -0700 (PDT)","from nuthatch (ip-77-48-47-2.net.vodafone.cz. [77.48.47.2])\n\tby smtp.gmail.com with ESMTPSA id\n\th11-20020a05600c314b00b0041490467febsm4609934wmo.38.2024.03.28.01.12.50\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tThu, 28 Mar 2024 01:12:50 -0700 (PDT)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=redhat.com header.i=@redhat.com\n\theader.b=\"UZqJm2r+\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n\ts=mimecast20190719; t=1711613575;\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\tin-reply-to:in-reply-to:references:references;\n\tbh=ZPclODB8fowo3wJ+lUeWpHTrphtN9gneiBIn/UyddmQ=;\n\tb=UZqJm2r+eE1rFJnVRhPYopXP95J6THevL0okQbYWhBYGHJW/qP/kZV/mN81lV7qmHEvyBP\n\thzKj7Fe4IYd/tM+fejqD5ck7S2LA9us5Jzfcho+uCu89M8VePGG2+g0sG8/7EQgKG4owot\n\twll+FyFBypBD6xQbpnoEtJQ1Uf6B7AU=","X-MC-Unique":"lEZkRGF9O-Sdkl7Q9XPXdA-1","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1711613572; x=1712218372;\n\th=mime-version:user-agent:message-id:date:references:in-reply-to\n\t:subject:cc:to:from:x-gm-message-state:from:to:cc:subject:date\n\t:message-id:reply-to;\n\tbh=ZPclODB8fowo3wJ+lUeWpHTrphtN9gneiBIn/UyddmQ=;\n\tb=DmOHSkprQR3lHzwDUhrygG12ed4lX0hFV79fYvuPI9NFtbO7SMy0sIAWdIRVJO6opR\n\txA8GG1LfIzCaMg4NhR89tL5StdTEm3TrAsMN46IpqZF8KfjVXBfhxPOwtUo48hglTllU\n\taHKvtU5xlvi9Tg7NotgpjQwOJn06/WdQciphC2+CI57AwkLvaJ2ZzckdV27EkM0Msw5W\n\tCV2cqh8YkuhvkuXwNtV9Foqf+hvbSIp4MbxeCIM/vDBfm4Zg2gSZQc56bCxvaduBi5W6\n\twAfZ0SLMCrTugieEZUEQG7s8qwdbYFfczPtlJCroBY2ox4cNMvYq2rPOV/Rl2GfpzS4e\n\twIrQ==","X-Gm-Message-State":"AOJu0YzbNAmHQPWfhqBEf11CKWLCyslQ6lz+tXlouRDL/YLBEQj2gSvG\n\tFuVvzvxZGRscVuxTF/0Y4FoCSHjUShZnnCnEaltYrG0WDEOTosGABNhd6cxtq2gOc6BVDCkZPTd\n\t8sGoAMXx4E+Kga6ksTOhPRJZ5HOwrxYfxEDm3E02HO4IHJdeW5mKofxp640tS4wiD2TbSAxo=","X-Received":["by 2002:a05:600c:354c:b0:414:8e39:7331 with SMTP id\n\ti12-20020a05600c354c00b004148e397331mr2161258wmq.3.1711613571883; \n\tThu, 28 Mar 2024 01:12:51 -0700 (PDT)","by 2002:a05:600c:354c:b0:414:8e39:7331 with SMTP id\n\ti12-20020a05600c354c00b004148e397331mr2161237wmq.3.1711613571466; \n\tThu, 28 Mar 2024 01:12:51 -0700 (PDT)"],"X-Google-Smtp-Source":"AGHT+IGp2ZVKQrhI1b+Y8b8m50J5olc/zmPQPIr4sFZdbF1uXgRD2VmRNaVWtZHX5MZSTwEA2Ug4YA==","From":"Milan Zamazal <mzamazal@redhat.com>","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org,  Kieran Bingham\n\t<kieran.bingham@ideasonboard.com>","Subject":"Re: [PATCH v4 1/1] apps: cam: Add support for PPM output format","In-Reply-To":"<20240328011153.GE21080@pendragon.ideasonboard.com> (Laurent\n\tPinchart's message of \"Thu, 28 Mar 2024 03:11:53 +0200\")","References":"<20240322195032.1803017-1-mzamazal@redhat.com>\n\t<20240322195032.1803017-2-mzamazal@redhat.com>\n\t<20240328010623.GD21080@pendragon.ideasonboard.com>\n\t<20240328011153.GE21080@pendragon.ideasonboard.com>","Date":"Thu, 28 Mar 2024 09:12:50 +0100","Message-ID":"<87plveixkt.fsf@redhat.com>","User-Agent":"Gnus/5.13 (Gnus v5.13)","MIME-Version":"1.0","X-Mimecast-Spam-Score":"0","X-Mimecast-Originator":"redhat.com","Content-Type":"text/plain","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>"}}]