Patch Detail
Show a patch.
GET /api/patches/24519/?format=api
{ "id": 24519, "url": "https://patchwork.libcamera.org/api/patches/24519/?format=api", "web_url": "https://patchwork.libcamera.org/patch/24519/", "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": "<20250930122726.1837524-21-stefan.klug@ideasonboard.com>", "date": "2025-09-30T12:26:41", "name": "[v1,20/33] libcamera: converter: Add dw100 vertex map class", "commit_ref": null, "pull_url": null, "state": "superseded", "archived": false, "hash": "7cfd4e16d3ec06b9374a6e1d6106a726b9993b63", "submitter": { "id": 184, "url": "https://patchwork.libcamera.org/api/people/184/?format=api", "name": "Stefan Klug", "email": "stefan.klug@ideasonboard.com" }, "delegate": null, "mbox": "https://patchwork.libcamera.org/patch/24519/mbox/", "series": [ { "id": 5468, "url": "https://patchwork.libcamera.org/api/series/5468/?format=api", "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=5468", "date": "2025-09-30T12:26:21", "name": "Full dewarper support on imx8mp", "version": 1, "mbox": "https://patchwork.libcamera.org/series/5468/mbox/" } ], "comments": "https://patchwork.libcamera.org/api/patches/24519/comments/", "check": "pending", "checks": "https://patchwork.libcamera.org/api/patches/24519/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 CF00EC328C\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 30 Sep 2025 13:15:35 +0000 (UTC)", "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id E205E6B599;\n\tTue, 30 Sep 2025 15:15:34 +0200 (CEST)", "from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 2C91462C35\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 30 Sep 2025 15:15:33 +0200 (CEST)", "from ideasonboard.com (unknown [94.31.94.171])\n\tby perceval.ideasonboard.com (Postfix) with UTF8SMTPSA id CEE01226;\n\tTue, 30 Sep 2025 15:14:04 +0200 (CEST)" ], "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"iW50MnI5\"; dkim-atps=neutral", "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1759238044;\n\tbh=iaZePcASm/J0ayqJaNJFOLLjY52UvzvyYVvuCKXP1Ws=;\n\th=From:To:Cc:Subject:Date:In-Reply-To:References:From;\n\tb=iW50MnI5KYO6r1s07HZUB4uEsxTPfvQRAZ3NPoM8PRqPW5+3NYVoiekWOiQthtrra\n\t3oSfgZO5qvJPQKA8VuiQz1BoFP8DAOQNn4KoaKNKQbtAyuT/wnlDa3W3Hnc+O8Pg9q\n\tQHiHS+wiDg2AUteFm+IwLOF0ym9oaShjWU+/vrgM=", "From": "Stefan Klug <stefan.klug@ideasonboard.com>", "To": "libcamera-devel@lists.libcamera.org", "Cc": "Stefan Klug <stefan.klug@ideasonboard.com>", "Subject": "[PATCH v1 20/33] libcamera: converter: Add dw100 vertex map class", "Date": "Tue, 30 Sep 2025 14:26:41 +0200", "Message-ID": "<20250930122726.1837524-21-stefan.klug@ideasonboard.com>", "X-Mailer": "git-send-email 2.48.1", "In-Reply-To": "<20250930122726.1837524-1-stefan.klug@ideasonboard.com>", "References": "<20250930122726.1837524-1-stefan.klug@ideasonboard.com>", "MIME-Version": "1.0", "Content-Transfer-Encoding": "8bit", "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": "Using a custom vertex map the dw100 dewarper is capable of doing\ncomplex and useful transformations on the image data. This class\nimplements a pipeline featuring:\n- Arbitrary ScalerCrop\n- Full transform support (Flip, 90deg rotations)\n- Arbitrary move, scale, rotate\n\nScalerCrop and Transform is implemented to provide a interface that is\nstandardized libcamera wide. The rest is implemented on top for more\nflexible dw100 specific features.\n\nSigned-off-by: Stefan Klug <stefan.klug@ideasonboard.com>\n\n---\n\nChanges in v0.9\n- Include header in meson.build\n- Fix black line at top and left when rotation 180 degrees\n\nChanges in v0.8\n- Cleanup & formatting\n\nChanges in v0.5\n- Fix crash in std::clamp() due to rounding errors\n---\n .../converter/converter_dw100_vertexmap.h | 80 ++++\n .../libcamera/internal/converter/meson.build | 1 +\n .../converter/converter_dw100_vertexmap.cpp | 362 ++++++++++++++++++\n src/libcamera/converter/meson.build | 1 +\n 4 files changed, 444 insertions(+)\n create mode 100644 include/libcamera/internal/converter/converter_dw100_vertexmap.h\n create mode 100644 src/libcamera/converter/converter_dw100_vertexmap.cpp", "diff": "diff --git a/include/libcamera/internal/converter/converter_dw100_vertexmap.h b/include/libcamera/internal/converter/converter_dw100_vertexmap.h\nnew file mode 100644\nindex 000000000000..9c6e0ffa4513\n--- /dev/null\n+++ b/include/libcamera/internal/converter/converter_dw100_vertexmap.h\n@@ -0,0 +1,80 @@\n+#pragma once\n+\n+#include <algorithm>\n+#include <assert.h>\n+#include <cmath>\n+#include <iostream>\n+#include <stdint.h>\n+#include <vector>\n+\n+#include <libcamera/base/span.h>\n+\n+#include <libcamera/geometry.h>\n+#include <libcamera/transform.h>\n+\n+namespace libcamera {\n+\n+class Dw100VertexMap\n+{\n+public:\n+\tenum ScaleMode {\n+\t\tFill = 0,\n+\t\tCrop = 1,\n+\t};\n+\n+\tvoid applyLimits();\n+\tvoid setInputSize(const Size &size)\n+\t{\n+\t\tinputSize_ = size;\n+\t\tscalerCrop_ = Rectangle(size);\n+\t}\n+\n+\tvoid setSensorCrop(const Rectangle &rect) { sensorCrop_ = rect; }\n+\n+\tvoid setScalerCrop(const Rectangle &rect) { scalerCrop_ = rect; }\n+\tconst Rectangle &effectiveScalerCrop() const { return effectiveScalerCrop_; }\n+\tstd::pair<Rectangle, Rectangle> scalerCropBounds() const\n+\t{\n+\t\treturn { Rectangle(sensorCrop_.x, sensorCrop_.y, 1, 1),\n+\t\t\t sensorCrop_ };\n+\t}\n+\n+\tvoid setOutputSize(const Size &size) { outputSize_ = size; }\n+\tconst Size &outputSize() const { return outputSize_; }\n+\n+\tvoid setTransform(const Transform &transform) { transform_ = transform; }\n+\tconst Transform &transform() const { return transform_; }\n+\n+\tvoid setScale(const float scale) { scale_ = scale; }\n+\tfloat effectiveScale() const { return (effectiveScaleX_ + effectiveScaleY_) * 0.5; }\n+\n+\tvoid setRotation(const float rotation) { rotation_ = rotation; }\n+\tfloat rotation() const { return rotation_; }\n+\n+\tvoid setOffset(const Point &offset) { offset_ = offset; }\n+\tconst Point &effectiveOffset() const { return effectiveOffset_; }\n+\n+\tvoid setMode(const ScaleMode mode) { mode_ = mode; }\n+\tScaleMode mode() const { return mode_; }\n+\n+\tstd::vector<uint32_t> getVertexMap();\n+\n+private:\n+\tint getVerticesForLength(const int length);\n+\n+\tRectangle scalerCrop_;\n+\tRectangle sensorCrop_;\n+\tTransform transform_ = Transform::Identity;\n+\tSize inputSize_;\n+\tSize outputSize_;\n+\tPoint offset_;\n+\tdouble scale_ = 1.0;\n+\tdouble rotation_ = 0.0;\n+\tScaleMode mode_ = Fill;\n+\tdouble effectiveScaleX_;\n+\tdouble effectiveScaleY_;\n+\tPoint effectiveOffset_;\n+\tRectangle effectiveScalerCrop_;\n+};\n+\n+} /* namespace libcamera */\ndiff --git a/include/libcamera/internal/converter/meson.build b/include/libcamera/internal/converter/meson.build\nindex 85007a4b0f8b..128c644cb73f 100644\n--- a/include/libcamera/internal/converter/meson.build\n+++ b/include/libcamera/internal/converter/meson.build\n@@ -2,5 +2,6 @@\n \n libcamera_internal_headers += files([\n 'converter_dw100.h',\n+ 'converter_dw100_vertexmap.h',\n 'converter_v4l2_m2m.h',\n ])\ndiff --git a/src/libcamera/converter/converter_dw100_vertexmap.cpp b/src/libcamera/converter/converter_dw100_vertexmap.cpp\nnew file mode 100644\nindex 000000000000..10d9e34d98c5\n--- /dev/null\n+++ b/src/libcamera/converter/converter_dw100_vertexmap.cpp\n@@ -0,0 +1,362 @@\n+#include \"libcamera/internal/converter/converter_dw100_vertexmap.h\"\n+\n+#include <algorithm>\n+#include <assert.h>\n+#include <cmath>\n+#include <stdint.h>\n+#include <tuple>\n+#include <vector>\n+\n+#include <libcamera/base/log.h>\n+#include <libcamera/base/span.h>\n+\n+#include <libcamera/geometry.h>\n+\n+#include \"libcamera/internal/vector.h\"\n+\n+constexpr int kDw100BlockSize = 16;\n+\n+namespace libcamera {\n+\n+LOG_DECLARE_CATEGORY(Converter)\n+\n+using Vector2d = Vector<double, 2>;\n+\n+namespace {\n+void applyTransform(const Transform &t, const double w, const double h,\n+\t\t Vector2d &p)\n+{\n+\tif (!!(t & Transform::HFlip)) {\n+\t\tp.x() = w - p.x();\n+\t}\n+\n+\tif (!!(t & Transform::VFlip)) {\n+\t\tp.y() = h - p.y();\n+\t}\n+\n+\tif (!!(t & Transform::Transpose)) {\n+\t\tstd::swap(p.x(), p.y());\n+\t}\n+}\n+\n+void applyTransform(const Transform &t, Rectangle &s)\n+{\n+\tif (!!(t & Transform::Transpose)) {\n+\t\tstd::swap(s.width, s.height);\n+\t}\n+}\n+\n+int roundTowardsZero(double v)\n+{\n+\tdouble r = (v < 0) ? std::ceil(v) : std::floor(v);\n+\treturn static_cast<int>(r);\n+}\n+\n+Vector2d rotatedRectSize(const Vector2d &size, const double rad)\n+{\n+\tdouble sa = sin(rad);\n+\tdouble ca = cos(rad);\n+\n+\treturn { { std::abs(size.x() * ca) + std::abs(size.y() * sa),\n+\t\t std::abs(size.x() * sa) + std::abs(size.y() * ca) } };\n+}\n+\n+Vector2d point2Vec2d(const Point &p)\n+{\n+\treturn { { static_cast<double>(p.x), static_cast<double>(p.y) } };\n+}\n+\n+} /* namespace */\n+\n+/**\n+ * The vertex map class represents a helper for handling dewarper vertex maps.\n+ * There are 3 important sizes in the system:\n+ *\n+ * - The Sensor size. The number of pixels of the whole sensor (Todo specify if\n+ * OB lines are included)\n+ * - The input Rectangle to the dewarper. Describes the pixel data\n+ * flowing into the dewarper in sensor space\n+ * - ScalerCrop rectangle. The rectangle that shall be used for all further\n+ * stages. It is applied after lens dewarping but is in sensor coordinate\n+ * space.\n+ * - The output size. This defines the size, the dewarper should output.\n+ *\n+ * +------------------------+\n+ * |Sensor size |\n+ * | |\n+ * | +----------------+ |\n+ * | | Input rect | |\n+ * | | | |\n+ * | +----------------+ |\n+ * +------------------------+\n+ *\n+ * This class implements a vertex map that forms the following pipeline:\n+ *\n+ * +-------------+ +-------------+ +-----------+ +--------------------+\n+ * | Lens Dewarp | -> | Scaler Crop | -> | Transform | -> | Offset, Scale, Rot |\n+ * +-------------+ +-------------+ +-----------+ +--------------------+\n+ *\n+ * ToDo: LensDewarp is not yet implemented. An identity map is used instead.\n+ *\n+ * All parameters are clamped to valid values before creating the vertex map.\n+ *\n+ * The constrains process works as follows:\n+ * - The ScalerCrop rectangle is clamped to the input rectangle\n+ * - The ScalerCrop rectangle is transformed by the specified transform\n+ * forming ScalerCropT\n+ * - A rectangle of output size is placed in the center of ScalerCropT\n+ * (OutputRect).\n+ * - Rotate gets applied to OutputRect,\n+ * - Scale is applied, but clamped so that the OutputRect fits completely into\n+ * ScalerCropT (Only regarding dimensions, not position)\n+ * - Offset is clamped so that the OutputRect lies inside ScalerCropT\n+ *\n+ * The lens dewarp map is usually calibrated during tuning and is a map that\n+ * maps from incoming pixels to dewarped pixels.\n+ *\n+ * Dewarp modes:\n+ * - Crop: Fills the output frame with the data from ScalerCropT\n+ * - Preserves aspect ratio\n+ * - Rotate and Offset and Scale are taken into account within the limits.\n+ * - Fill:\n+ * - Fills the output frame with the data from ScalerCropT.\n+ * - Does not preserve aspect ratio\n+ * - Rotate is taken into account\n+ *\n+ */\n+void Dw100VertexMap::applyLimits()\n+{\n+\tint ow = outputSize_.width;\n+\tint oh = outputSize_.height;\n+\teffectiveScalerCrop_ = scalerCrop_.boundedTo(sensorCrop_);\n+\n+\t/* Map the scalerCrop to the input pixel space */\n+\tRectangle localScalerCropT = effectiveScalerCrop_.transformedBetween(\n+\t\tsensorCrop_, Rectangle(inputSize_));\n+\n+\tapplyTransform(transform_, localScalerCropT);\n+\n+\tdouble rad = rotation_ / 180.0 * M_PI;\n+\tdouble sa = sin(rad);\n+\tdouble ca = cos(rad);\n+\n+\tdouble scale = scale_;\n+\tVector2d offset;\n+\tVector2d size = rotatedRectSize(point2Vec2d(Point(ow, oh)), rad);\n+\tVector2d t;\n+\n+\t/*\n+ * Todo: All these rotations and calculations below are way easier using\n+ * matrices. So reimplement using matrix class.\n+ */\n+\n+\t/*\n+ * Calculate constraints\n+ */\n+\tif (mode_ == Crop) {\n+\t\t/* Scale up if needed */\n+\t\tscale = std::max(scale,\n+\t\t\t\t std::max(size.x() / localScalerCropT.width,\n+\t\t\t\t\t size.y() / localScalerCropT.height));\n+\t\teffectiveScaleX_ = scale;\n+\t\teffectiveScaleY_ = scale;\n+\n+\t\tsize = size / scale;\n+\n+\t\t/* Transform offset to sensor space */\n+\t\toffset.x() = ca * offset_.x - sa * offset_.y;\n+\t\toffset.y() = sa * offset_.x + ca * offset_.y;\n+\t\toffset = offset / scale;\n+\t} else if (mode_ == Fill) {\n+\t\t/*\n+\t\t * Calculate the best x and y scale values to fit the rotated\n+\t\t * localScalerCropT rectangle into the output rectangle.\n+\t\t */\n+\t\tdouble diff = (static_cast<double>(localScalerCropT.width) -\n+\t\t\t localScalerCropT.height) *\n+\t\t\t 0.5;\n+\t\tdouble middle = (static_cast<double>(localScalerCropT.width) +\n+\t\t\t\t localScalerCropT.height) *\n+\t\t\t\t0.5;\n+\t\tdouble w = middle + cos(rad * 2) * diff;\n+\t\tdouble h = middle - cos(rad * 2) * diff;\n+\n+\t\tsize = rotatedRectSize(Vector2d{ { w, h } }, rad);\n+\t\tscale = std::max(size.x() / localScalerCropT.width,\n+\t\t\t\t size.y() / localScalerCropT.height);\n+\n+\t\teffectiveScaleX_ = (ow / w) * scale;\n+\t\teffectiveScaleY_ = (oh / h) * scale;\n+\n+\t\tsize = size / scale;\n+\n+\t\tt = point2Vec2d(offset_);\n+\t\tt.x() /= effectiveScaleX_;\n+\t\tt.y() /= effectiveScaleY_;\n+\n+\t\t/* Transform offset to sensor space including local scale */\n+\t\toffset.x() = ca * t.x() - sa * t.y();\n+\t\toffset.y() = sa * t.x() + ca * t.y();\n+\t} else {\n+\t\tLOG(Converter, Error) << \"Unknown mode \" << mode_;\n+\t\treturn;\n+\t}\n+\n+\t/* Clamp offset in input space. */\n+\tdouble maxoff;\n+\t/*\n+\t * Due to rounding errors, size might be slightly bigger than scaler\n+\t * crop. Clamp the offset to 0 to prevent a crash in the next clamp.\n+\t */\n+\tmaxoff = std::max(0.0, (localScalerCropT.width - size.x())) * 0.5;\n+\toffset.x() = std::clamp(offset.x(), -maxoff, maxoff);\n+\tmaxoff = std::max(0.0, (localScalerCropT.height - size.y())) * 0.5;\n+\toffset.y() = std::clamp(offset.y(), -maxoff, maxoff);\n+\n+\t/*\n+\t * Transform offset back into output space.\n+\t * Note the transposed rotation matrix.\n+\t */\n+\tt = offset;\n+\toffset.x() = ca * t.x() + sa * t.y();\n+\toffset.y() = -sa * t.x() + ca * t.y();\n+\toffset.x() *= effectiveScaleX_;\n+\toffset.y() *= effectiveScaleY_;\n+\n+\teffectiveOffset_ = Point(roundTowardsZero(offset.x()),\n+\t\t\t\t roundTowardsZero(offset.y()));\n+}\n+\n+std::vector<uint32_t> Dw100VertexMap::getVertexMap()\n+{\n+\tint ow = outputSize_.width;\n+\tint oh = outputSize_.height;\n+\tint tileCountW = getVerticesForLength(ow);\n+\tint tileCountH = getVerticesForLength(oh);\n+\tdouble rad = rotation_ / 180.0 * M_PI;\n+\tdouble sa = sin(rad);\n+\tdouble ca = cos(rad);\n+\n+\tapplyLimits();\n+\n+\t/*\n+\t * libcamera handles all crop rectangles in sensor space. But the\n+\t * dewarper \"sees\" only the pixels it gets passed. Note that these might\n+\t * not cover exactly the max sensor crop, as there might be a crop\n+\t * between ISP and dewarper to crop to a format supported by the\n+\t * dewarper. effectiveScalerCrop_ is the crop in sensor space that gets\n+\t * fed into the dewarper. localScalerCrop is the sensor crop mapped to\n+\t * the data that is fed into the dewarper.\n+\t */\n+\tRectangle localScalerCrop = effectiveScalerCrop_.transformedBetween(\n+\t\tsensorCrop_, Rectangle(inputSize_));\n+\tRectangle localScalerCropT = localScalerCrop;\n+\tapplyTransform(transform_, localScalerCropT);\n+\n+\t/*\n+\t * The dw100 has a specialty in interpolation that has to be taken into\n+\t * account to use in a pixel perfect manner. To explain this, I will\n+\t * only use the x direction, the vertical axis behaves the same.\n+\t *\n+\t * Let's start with a pixel perfect 1:1 mapping of an image with a width\n+\t * of 64pixels. The coordinates of the vertex map would then be:\n+\t * 0 -- 16 -- 32 -- 48 -- 64\n+\t * Note how the last coordinate lies outside the image (which ends at\n+\t * 63) as it is basically the beginning of the next macro block.\n+\t *\n+\t * if we zoom out a bit we might end up with something like\n+\t * -10 -- 0 -- 32 -- 64 -- 74\n+\t * As the dewarper coordinates are unsigned it actually sees\n+\t * 0 -- 0 -- 32 -- 64 -- 74\n+\t * Leading to stretched pixels at the beginning and black for everything\n+\t * > 63\n+\t *\n+\t * Now lets rotate the image by 180 degrees. A trivial rotation would\n+\t * end up with:\n+\t *\n+\t * 64 -- 48 -- 32 -- 16 -- 0\n+\t *\n+\t * But as the first column now points to pixel 64 we get a single black\n+\t * line. So for a proper 180* rotation, the coordinates need to be\n+\t *\n+\t * 63 -- 47 -- 31 -- 15 -- -1\n+\t *\n+\t * The -1 is clamped to 0 again, leading to a theoretical slight\n+\t * interpolation error on the last 16 pixels.\n+\t *\n+\t * To create this proper transformation there are two things todo:\n+\t *\n+\t * 1. The rotation centers are offset by -0.5. This evens out for no rotation,\n+\t * and leads to a coordinate offset of -1 on 180 degree rotations.\n+\t *\n+\t * 2. The transformation (flip and transpose) need to act on a width-1\n+\t * to get the same effect.\n+\t */\n+\tVector2d centerS{ { localScalerCropT.width * 0.5 - 0.5,\n+\t\t\t localScalerCropT.height * 0.5 - 0.5 } };\n+\tVector2d centerD{ { ow * 0.5 - 0.5,\n+\t\t\t oh * 0.5 - 0.5 } };\n+\n+\tLOG(Converter, Debug)\n+\t\t<< \"Apply vertex map for\"\n+\t\t<< \" inputSize: \" << inputSize_\n+\t\t<< \" outputSize: \" << outputSize_\n+\t\t<< \" Transform: \" << transformToString(transform_)\n+\t\t<< \"\\n effectiveScalerCrop: \" << effectiveScalerCrop_\n+\t\t<< \" localScalerCropT: \" << localScalerCropT\n+\t\t<< \" scaleX: \" << effectiveScaleX_\n+\t\t<< \" scaleY: \" << effectiveScaleX_\n+\t\t<< \" rotation: \" << rotation_\n+\t\t<< \" offset: \" << effectiveOffset_;\n+\n+\t/*\n+\t * For every output tile, calculate the position of the corners in the\n+\t * input image.\n+\t */\n+\tstd::vector<uint32_t> res;\n+\tres.reserve(tileCountW * tileCountH);\n+\tfor (int y = 0; y < tileCountH; y++) {\n+\t\tfor (int x = 0; x < tileCountW; x++) {\n+\t\t\tVector2d p{ { static_cast<double>(x) * kDw100BlockSize,\n+\t\t\t\t static_cast<double>(y) * kDw100BlockSize } };\n+\t\t\tp = p.max(0.0).min(Vector2d{ { static_cast<double>(ow),\n+\t\t\t\t\t\t static_cast<double>(oh) } });\n+\n+\t\t\t/*\n+\t\t\t * Transform into coordinates centered on the output\n+\t\t\t * rectangle + offset.\n+\t\t\t */\n+\t\t\tp = p - centerD + point2Vec2d(effectiveOffset_);\n+\t\t\tp.x() /= effectiveScaleX_;\n+\t\t\tp.y() /= effectiveScaleY_;\n+\n+\t\t\t/* Rotate */\n+\t\t\tVector2d p2;\n+\t\t\tp2.x() = ca * p.x() - sa * p.y();\n+\t\t\tp2.y() = sa * p.x() + ca * p.y();\n+\n+\t\t\t/* Transform to localScalerCropT space. */\n+\t\t\tp = p2 + centerS;\n+\t\t\t/* Subtract -1 for the mapping documented above. */\n+\t\t\tapplyTransform(-transform_,\n+\t\t\t\t localScalerCropT.width - 1,\n+\t\t\t\t localScalerCropT.height - 1, p);\n+\t\t\tp.x() += localScalerCrop.x;\n+\t\t\tp.y() += localScalerCrop.y;\n+\n+\t\t\t/* Convert to fixed point */\n+\t\t\tuint32_t v = static_cast<uint32_t>(p.y() * 16) << 16 |\n+\t\t\t\t (static_cast<uint32_t>(p.x() * 16) & 0xffff);\n+\t\t\tres.push_back(v);\n+\t\t}\n+\t}\n+\n+\treturn res;\n+}\n+\n+int Dw100VertexMap::getVerticesForLength(const int length)\n+{\n+\treturn (length + kDw100BlockSize - 1) / kDw100BlockSize + 1;\n+}\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/converter/meson.build b/src/libcamera/converter/meson.build\nindex fe2dcebb67da..9f59b57c26b9 100644\n--- a/src/libcamera/converter/meson.build\n+++ b/src/libcamera/converter/meson.build\n@@ -1,6 +1,7 @@\n # SPDX-License-Identifier: CC0-1.0\n \n libcamera_internal_sources += files([\n+ 'converter_dw100_vertexmap.cpp',\n 'converter_dw100.cpp',\n 'converter_v4l2_m2m.cpp'\n ])\n", "prefixes": [ "v1", "20/33" ] }