From patchwork Thu Oct 23 14:48:21 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Stefan Klug X-Patchwork-Id: 24747 Return-Path: X-Original-To: parsemail@patchwork.libcamera.org Delivered-To: parsemail@patchwork.libcamera.org Received: from lancelot.ideasonboard.com (lancelot.ideasonboard.com [92.243.16.209]) by patchwork.libcamera.org (Postfix) with ESMTPS id 4AADFC3334 for ; Thu, 23 Oct 2025 14:49:48 +0000 (UTC) Received: from lancelot.ideasonboard.com (localhost [IPv6:::1]) by lancelot.ideasonboard.com (Postfix) with ESMTP id 01C586083A; Thu, 23 Oct 2025 16:49:48 +0200 (CEST) Authentication-Results: lancelot.ideasonboard.com; dkim=pass (1024-bit key; unprotected) header.d=ideasonboard.com header.i=@ideasonboard.com header.b="q2j6+grh"; dkim-atps=neutral Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lancelot.ideasonboard.com (Postfix) with ESMTPS id 9138F6081B for ; Thu, 23 Oct 2025 16:49:46 +0200 (CEST) Received: from ideasonboard.com (unknown [IPv6:2a00:6020:448c:6c00:7328:357b:4ce1:72b6]) by perceval.ideasonboard.com (Postfix) with UTF8SMTPSA id 841631127; Thu, 23 Oct 2025 16:48:01 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1761230881; bh=LbhVCKPmpquMvTodiUYckjrhEt0/lz50k6XNAmtyidg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=q2j6+grh5C3iKJIJZ4WL2LYKeoyr945BNugpty5zDM7Q7rZI8NCH+W4FGpyu9JkcI aqyGpXpIL0yMIJiptXu1hNZ7RN98AMSFlYop3OsHtoWLzQxH4RTLrFpbGxpuWEdPV2 dlbIekmMQGzCPuR/bh8g2vubwLFNoRUNwUUKRfGM= From: Stefan Klug To: libcamera-devel@lists.libcamera.org Cc: Stefan Klug Subject: [PATCH v2 20/35] libcamera: converter: Add dw100 vertex map class Date: Thu, 23 Oct 2025 16:48:21 +0200 Message-ID: <20251023144841.403689-21-stefan.klug@ideasonboard.com> X-Mailer: git-send-email 2.48.1 In-Reply-To: <20251023144841.403689-1-stefan.klug@ideasonboard.com> References: <20251023144841.403689-1-stefan.klug@ideasonboard.com> MIME-Version: 1.0 X-BeenThere: libcamera-devel@lists.libcamera.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libcamera-devel-bounces@lists.libcamera.org Sender: "libcamera-devel" Using a custom vertex map the dw100 dewarper is capable of doing complex and useful transformations on the image data. This class implements a pipeline featuring: - Arbitrary ScalerCrop - Full transform support (Flip, 90deg rotations) - Arbitrary move, scale, rotate ScalerCrop and Transform is implemented to provide a interface that is standardized libcamera wide. The rest is implemented on top for more flexible dw100 specific features. Signed-off-by: Stefan Klug --- Changes in v2: - Replaced manual transforms with an affine transformation matrix - Changed rotation direction to be in sync with the rotation in CameraConfiguration::orientation - Changed offset parameter to be in ScalerCrop coordinates. This is easier to explain and has the added benefit, that Scale/Rotate is always centered to the visible image. - Improved code comments - Make dw100VerticesForLength a local function - Dropped unnecessary includes - Added documentation Changes in v0.9 - Include header in meson.build - Fix black line at top and left when rotation 180 degrees Changes in v0.8 - Cleanup & formatting Changes in v0.5 - Fix crash in std::clamp() due to rounding errors --- .../converter/converter_dw100_vertexmap.h | 76 +++ .../libcamera/internal/converter/meson.build | 1 + .../converter/converter_dw100_vertexmap.cpp | 566 ++++++++++++++++++ src/libcamera/converter/meson.build | 1 + 4 files changed, 644 insertions(+) create mode 100644 include/libcamera/internal/converter/converter_dw100_vertexmap.h create mode 100644 src/libcamera/converter/converter_dw100_vertexmap.cpp diff --git a/include/libcamera/internal/converter/converter_dw100_vertexmap.h b/include/libcamera/internal/converter/converter_dw100_vertexmap.h new file mode 100644 index 000000000000..e72cb72bb9f1 --- /dev/null +++ b/include/libcamera/internal/converter/converter_dw100_vertexmap.h @@ -0,0 +1,76 @@ +#pragma once + +#include +#include +#include +#include + +#include + +#include +#include + +namespace libcamera { + +class Dw100VertexMap +{ +public: + enum ScaleMode { + Fill = 0, + Crop = 1, + }; + + void applyLimits(); + void setInputSize(const Size &size) + { + inputSize_ = size; + scalerCrop_ = Rectangle(size); + } + + void setSensorCrop(const Rectangle &rect) { sensorCrop_ = rect; } + + void setScalerCrop(const Rectangle &rect) { scalerCrop_ = rect; } + const Rectangle &effectiveScalerCrop() const { return effectiveScalerCrop_; } + std::pair scalerCropBounds() const + { + return { Rectangle(sensorCrop_.x, sensorCrop_.y, 1, 1), + sensorCrop_ }; + } + + void setOutputSize(const Size &size) { outputSize_ = size; } + const Size &outputSize() const { return outputSize_; } + + void setTransform(const Transform &transform) { transform_ = transform; } + const Transform &transform() const { return transform_; } + + void setScale(const float scale) { scale_ = scale; } + float effectiveScale() const { return (effectiveScaleX_ + effectiveScaleY_) * 0.5; } + + void setRotation(const float rotation) { rotation_ = rotation; } + float rotation() const { return rotation_; } + + void setOffset(const Point &offset) { offset_ = offset; } + const Point &effectiveOffset() const { return effectiveOffset_; } + + void setMode(const ScaleMode mode) { mode_ = mode; } + ScaleMode mode() const { return mode_; } + + std::vector getVertexMap(); + +private: + Rectangle scalerCrop_; + Rectangle sensorCrop_; + Transform transform_ = Transform::Identity; + Size inputSize_; + Size outputSize_; + Point offset_; + double scale_ = 1.0; + double rotation_ = 0.0; + ScaleMode mode_ = Fill; + double effectiveScaleX_; + double effectiveScaleY_; + Point effectiveOffset_; + Rectangle effectiveScalerCrop_; +}; + +} /* namespace libcamera */ diff --git a/include/libcamera/internal/converter/meson.build b/include/libcamera/internal/converter/meson.build index 85007a4b0f8b..128c644cb73f 100644 --- a/include/libcamera/internal/converter/meson.build +++ b/include/libcamera/internal/converter/meson.build @@ -2,5 +2,6 @@ libcamera_internal_headers += files([ 'converter_dw100.h', + 'converter_dw100_vertexmap.h', 'converter_v4l2_m2m.h', ]) diff --git a/src/libcamera/converter/converter_dw100_vertexmap.cpp b/src/libcamera/converter/converter_dw100_vertexmap.cpp new file mode 100644 index 000000000000..0e930479b6f7 --- /dev/null +++ b/src/libcamera/converter/converter_dw100_vertexmap.cpp @@ -0,0 +1,566 @@ +#include "libcamera/internal/converter/converter_dw100_vertexmap.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "libcamera/internal/vector.h" + +constexpr int kDw100BlockSize = 16; + +namespace libcamera { + +LOG_DECLARE_CATEGORY(Converter) +namespace { + +using Vector2d = Vector; +using Vector3d = Vector; +using Matrix3x3 = Matrix; + +Matrix3x3 makeTranslate(const double tx, const double ty) +{ + Matrix3x3 m = Matrix3x3::identity(); + m[0][2] = tx; + m[1][2] = ty; + return m; +} + +Matrix3x3 makeTranslate(const Vector2d &t) +{ + return makeTranslate(t.x(), t.y()); +} + +Matrix3x3 makeRotate(const double degrees) +{ + double rad = degrees / 180.0 * M_PI; + double sa = std::sin(rad); + double ca = std::cos(rad); + + Matrix3x3 m = Matrix3x3::identity(); + m[0][0] = ca; + m[0][1] = -sa; + m[1][0] = sa; + m[1][1] = ca; + return m; +} + +Matrix3x3 makeScale(const double sx, const double sy) +{ + Matrix3x3 m = Matrix3x3::identity(); + m[0][0] = sx; + m[1][1] = sy; + return m; +} + +/** + * \param t The transform to apply + * \param size The size of the rectangle that is transformed + * + * Create a matrix that represents the transform done by the \a t. It assumes + * that the origin of the coordinate system is at the top left corner of of the + * rectangle. + */ +Matrix3x3 makeTransform(const Transform &t, const Size &size) +{ + Matrix3x3 m = Matrix3x3::identity(); + double wm = size.width * 0.5; + double hm = size.height * 0.5; + m = makeTranslate(-wm, -hm) * m; + + if (!!(t & Transform::HFlip)) + m = makeScale(-1, 1) * m; + + if (!!(t & Transform::VFlip)) + m = makeScale(1, -1) * m; + + if (!!(t & Transform::Transpose)) { + m = makeRotate(-90) * m; + m = makeScale(1, -1) * m; + std::swap(wm, hm); + } + + m = makeTranslate(wm, hm) * m; + + return m; +} + +/** + * \param from The source rectangle + * \param to The destination rectangle + * + * Create a matrix that transforms from the coordinate system of rectangle \a + * from into the coordinate system of rectangle \a to, by overlaying the + * rectangles. + * + * \see Rectangle::transformedBetween() + */ +Matrix3x3 makeTransform(const Rectangle &from, const Rectangle &to) +{ + Matrix3x3 m = Matrix3x3::identity(); + double sx = to.width / static_cast(from.width); + double sy = to.height / static_cast(from.height); + m = makeTranslate(-from.x, -from.y) * m; + m = makeScale(sx, sy) * m; + m = makeTranslate(to.x, to.y) * m; + return m; +} + +Vector2d transformPoint(const Matrix3x3 &m, const Vector2d &p) +{ + Vector3d p2{ { p.x(), p.y(), 1.0 } }; + p2 = m * p2; + return { { p2.x() / p2.z(), p2.y() / p2.z() } }; +} + +Vector2d transformVector(const Matrix3x3 &m, const Vector2d &p) +{ + Vector3d p2{ { p.x(), p.y(), 0.0 } }; + p2 = m * p2; + return { { p2.x(), p2.y() } }; +} + +Vector2d rotatedRectSize(const Vector2d &size, const double degrees) +{ + double rad = degrees / 180.0 * M_PI; + double sa = sin(rad); + double ca = cos(rad); + + return { { std::abs(size.x() * ca) + std::abs(size.y() * sa), + std::abs(size.x() * sa) + std::abs(size.y() * ca) } }; +} + +Vector2d point2Vec2d(const Point &p) +{ + return { { static_cast(p.x), static_cast(p.y) } }; +} + +int dw100VerticesForLength(const int length) +{ + return (length + kDw100BlockSize - 1) / kDw100BlockSize + 1; +} + +} /* namespace */ + +/** + * \class libcamera::Dw100VertexMap + * \brief Helper class to compute dw100 vertex maps + * + * The vertex map class represents a helper for handling dewarper vertex maps. + * There are 3 important sizes in the system: + * + * - The sensor size. The number of pixels of the whole sensor (\todo specify + * the crop rectangle). + * - The input rectangle to the dewarper. Describes the pixel data flowing into + * the dewarper in sensor coordinates. + * - ScalerCrop rectangle. The rectangle that shall be used for all further + * stages. It is applied after lens dewarping but is in sensor coordinate + * space. + * - The output size. This defines the size, the dewarper should output. + * + * +------------------------+ + * |Sensor size | + * | +----------------+ | + * | | Input rect | | + * | | +-------------+ | + * | | | ScalerCrop | | + * | | | | | + * | +--+-------------+ | + * +------------------------+ + * + * This class implements a vertex map that forms the following pipeline: + * + * +-------------+ +-------------+ +------------+ +-----------------+ + * | | -> | | | Transform | | Pan/Zoom | + * | Lens Dewarp | -> | Scaler Crop | -> | (H/V Flip, | -> | (Offset, Scale, | + * | | | | | Transpose) | | Rotate) | + * +-------------+ +-------------+ +------------+ +-----------------+ + * + * \todo Lens dewarp is not yet implemented. An identity map is used instead. + * + * All parameters are clamped to valid values before creating the vertex map. + * + * The constrains process works as follows: + * - The ScalerCrop rectangle is clamped to the input rectangle + * - The ScalerCrop rectangle is transformed by the specified transform + * forming ScalerCropT + * - A rectangle of output size is placed in the center of ScalerCropT + * (OutputRect). + * - Rotate gets applied to OutputRect, + * - Scale is applied, but clamped so that the OutputRect fits completely into + * ScalerCropT (Only regarding dimensions, not position) + * - Offset is clamped so that the OutputRect lies inside ScalerCropT + * + * The lens dewarp map is usually calibrated during tuning and is a map that + * maps from incoming pixels to dewarped pixels. + */ + +/** + * \enum Dw100VertexMap::ScaleMode + * \brief The scale modes available for a vertex map + * + * \var Dw100VertexMap::Fill + * \brief Scale the input to fill the output + * + * This scale mode does not preserve aspect ratio. Offset and rotation are taken + * into account. + * + * \var Dw100VertexMap::Crop + * \brief Crop the input + * + * This scale mode preserves the aspect ratio. Offset, scale, rotation are taken + * into account within the possible limits. + */ + +/** + * \brief Apply limits on scale and offset + * + * This function calculates \a effectiveScalerCrop_, \a effectiveScale_ and \a + * effectiveOffset_ based on the requested scaler crop, scale, rotation, offset + * and the selected scale mode, so that the whole output area is filled with + * valid input data. + */ +void Dw100VertexMap::applyLimits() +{ + int ow = outputSize_.width; + int oh = outputSize_.height; + effectiveScalerCrop_ = scalerCrop_.boundedTo(sensorCrop_); + + /* Map the scalerCrop to the input pixel space */ + Rectangle localScalerCrop = effectiveScalerCrop_.transformedBetween( + sensorCrop_, Rectangle(inputSize_)); + + Size localCropSizeT = localScalerCrop.size(); + if (!!(transform_ & Transform::Transpose)) + std::swap(localCropSizeT.width, localCropSizeT.height); + + Vector2d size = rotatedRectSize(point2Vec2d({ ow, oh }), rotation_); + + /* Calculate constraints */ + double scale = scale_; + if (mode_ == Crop) { + /* Scale up if needed */ + scale = std::max(scale, + std::max(size.x() / localCropSizeT.width, + size.y() / localCropSizeT.height)); + effectiveScaleX_ = scale; + effectiveScaleY_ = scale; + + size = size / scale; + + } else if (mode_ == Fill) { + effectiveScaleX_ = size.x() / localCropSizeT.width; + effectiveScaleY_ = size.y() / localCropSizeT.height; + + size.x() /= effectiveScaleX_; + size.y() /= effectiveScaleY_; + } else { + LOG(Converter, Error) << "Unknown mode " << mode_; + return; + } + + /* + * Clamp offset. Due to rounding errors, size might be slightly bigger + * than scaler crop. Clamp the offset to 0 to prevent a crash in the + * next clamp. + */ + double maxoffX, maxoffY; + maxoffX = std::max(0.0, (localCropSizeT.width - size.x())) * 0.5; + maxoffY = std::max(0.0, (localCropSizeT.height - size.y())) * 0.5; + if (!!(transform_ & Transform::Transpose)) + std::swap(maxoffX, maxoffY); + + /* + * Transform the offset from sensor space to local space, apply the + * limit and transform back. + */ + Vector2d offset = point2Vec2d(offset_); + Matrix3x3 m; + + m = makeTransform(effectiveScalerCrop_, localScalerCrop); + offset = transformVector(m, offset); + offset.x() = std::clamp(offset.x(), -maxoffX, maxoffX); + offset.y() = std::clamp(offset.y(), -maxoffY, maxoffY); + m = makeTransform(localScalerCrop, effectiveScalerCrop_); + offset = transformVector(m, offset); + effectiveOffset_.x = offset.x(); + effectiveOffset_.y = offset.y(); +} + +/** + * \fn Dw100VertexMap::setInputSize() + * \brief Set the size of the input data + * \param[in] size The input size + * + * To calculate a proper vertex map, the size of the input images must be set. + */ + +/** + * \fn Dw100VertexMap::setSensorCrop() + * \brief Set the crop rectangle that represents the input data + * \param[in] rect + * + * Set the rectangle that represents the input data in sensor coordinates. This + * must be specified to properly calculate the vertex map. + */ + +/** + * \fn Dw100VertexMap::setScalerCrop() + * \brief Set the requested scaler crop + * \param[in] rect + * + * Set the requested scaler crop. The actually applied scaler crop can be + * queried using \a Dw100VertexMap::effectiveScalerCrop() after calling + * Dw100VertexMap::applyLimits(). + */ + +/** + * \fn Dw100VertexMap::effectiveScalerCrop() + * \brief Get the effective scaler crop + * + * \return The effective scaler crop + */ + +/** + * \fn Dw100VertexMap::scalerCropBounds() + * \brief Get the min and max values for the scaler crop + * + * \return A pair of rectangles that represent the scaler crop min/max values + */ + +/** + * \fn Dw100VertexMap::setOutputSize() + * \brief Set the output size + * \param[in] size The size of the output images + */ + +/** + * \fn Dw100VertexMap::outputSize() + * \brief Get the output size + * \return The output size + */ + +/** + * \fn Dw100VertexMap::setTransform() + * \brief Sets the transform to apply + * \param[in] transform The transform + */ + +/** + * \fn Dw100VertexMap::transform() + * \brief Get the transform + * \return The transform + */ + +/** + * \fn Dw100VertexMap::setScale() + * \brief Sets the scale to apply + * \param[in] scale The scale + * + * Set the requested scale. The actually applied scale can be queried using \a + * Dw100VertexMap::effectiveScale() after calling \a + * Dw100VertexMap::applyLimits(). + */ + +/** + * \fn Dw100VertexMap::effectiveScale() + * \brief Get the effective scale + * + * Returns the actual scale applied to the input pixels in x and y direction. So + * a value of [2.0, 1.5] means that every input pixel is scaled to cover 2 + * output pixels in x-direction and 1.5 in y-direction. + * + * \return The effective scale + */ + +/** + * \fn Dw100VertexMap::setRotation() + * \brief Sets the rotation to apply + * \param[in] rotation The rotation in degrees + * + * The rotation is in clockwise direction to allow the same transform as + * CameraConfiguration::orientation + */ + +/** + * \fn Dw100VertexMap::rotation() + * \brief Get the rotation + * \return The rotation in degrees + */ + +/** + * \fn Dw100VertexMap::setOffset() + * \brief Sets the offset to apply + * \param[in] offset The offset + * + * Set the requested offset. The actually applied offset can be queried using \a + * Dw100VertexMap::effectiveOffset() after calling \a + * Dw100VertexMap::applyLimits(). + */ + +/** + * \fn Dw100VertexMap::effectiveOffset() + * \brief Get the effective offset + * + * Returns the actual offset applied to the input pixels in ScalerCrop + * coordinates. + * + * \return The effective offset + */ + +/** + * \fn Dw100VertexMap::setMode() + * \brief Sets the scaling mode to apply + * \param[in] mode The mode + */ + +/** + * \fn Dw100VertexMap::mode() + * \brief Get the scaling mode + * \return The scaling mode + */ + +/** + * \brief Get the dw100 vertex map + * + * Calculates the vertex map as a vector of hardware specific entries. + * + * \return The vertex map + */ +std::vector Dw100VertexMap::getVertexMap() +{ + int ow = outputSize_.width; + int oh = outputSize_.height; + int tileCountW = dw100VerticesForLength(ow); + int tileCountH = dw100VerticesForLength(oh); + + applyLimits(); + + /* + * libcamera handles all crop rectangles in sensor space. But the + * dewarper "sees" only the pixels it gets passed. Note that these might + * not cover exactly the max sensor crop, as there might be a crop + * between ISP and dewarper to crop to a format supported by the + * dewarper. effectiveScalerCrop_ is the crop in sensor space that gets + * fed into the dewarper. localScalerCrop is the sensor crop mapped to + * the data that is fed into the dewarper. + */ + Rectangle localScalerCrop = effectiveScalerCrop_.transformedBetween( + sensorCrop_, Rectangle(inputSize_)); + Size localCropSizeT = localScalerCrop.size(); + if (!!(transform_ & Transform::Transpose)) + std::swap(localCropSizeT.width, localCropSizeT.height); + + /* + * The dw100 has a specialty in interpolation that has to be taken into + * account to use in a pixel perfect manner. To explain this, I will + * only use the x direction, the vertical axis behaves the same. + * + * Let's start with a pixel perfect 1:1 mapping of an image with a width + * of 64pixels. The coordinates of the vertex map would then be: + * 0 -- 16 -- 32 -- 48 -- 64 + * Note how the last coordinate lies outside the image (which ends at + * 63) as it is basically the beginning of the next macro block. + * + * if we zoom out a bit we might end up with something like + * -10 -- 0 -- 32 -- 64 -- 74 + * As the dewarper coordinates are unsigned it actually sees + * 0 -- 0 -- 32 -- 64 -- 74 + * Leading to stretched pixels at the beginning and black for everything + * > 63 + * + * Now lets rotate the image by 180 degrees. A trivial rotation would + * end up with: + * + * 64 -- 48 -- 32 -- 16 -- 0 + * + * But as the first column now points to pixel 64 we get a single black + * line. So for a proper 180* rotation, the coordinates need to be + * + * 63 -- 47 -- 31 -- 15 -- -1 + * + * The -1 is clamped to 0 again, leading to a theoretical slight + * interpolation error on the last 16 pixels. + * + * To create this proper transformation there are two things todo: + * + * 1. The rotation centers are offset by -0.5. This evens out for no + * rotation, and leads to a coordinate offset of -1 on 180 degree + * rotations. + * 2. The transformation (flip and transpose) need to act on a size-1 + * to get the same effect. + */ + Vector2d centerS{ { localCropSizeT.width * 0.5 - 0.5, + localCropSizeT.height * 0.5 - 0.5 } }; + Vector2d centerD{ { ow * 0.5 - 0.5, + oh * 0.5 - 0.5 } }; + + LOG(Converter, Debug) + << "Apply vertex map for" + << " inputSize: " << inputSize_ + << " outputSize: " << outputSize_ + << " Transform: " << transformToString(transform_) + << "\n effectiveScalerCrop: " << effectiveScalerCrop_ + << " localCropSizeT: " << localCropSizeT + << " scaleX: " << effectiveScaleX_ + << " scaleY: " << effectiveScaleX_ + << " rotation: " << rotation_ + << " offset: " << effectiveOffset_; + + Matrix3x3 outputToSensor = Matrix3x3::identity(); + /* Move to center of output */ + outputToSensor = makeTranslate(-centerD) * outputToSensor; + outputToSensor = makeRotate(-rotation_) * outputToSensor; + outputToSensor = makeScale(1.0 / effectiveScaleX_, 1.0 / effectiveScaleY_) * outputToSensor; + /* Move to top left of localScalerCropT */ + outputToSensor = makeTranslate(centerS) * outputToSensor; + outputToSensor = makeTransform(-transform_, localCropSizeT.shrunkBy({ 1, 1 })) * + outputToSensor; + /* Transform from "within localScalerCrop" to input reference frame */ + outputToSensor = makeTranslate(localScalerCrop.x, localScalerCrop.y) * outputToSensor; + outputToSensor = makeTransform(localScalerCrop, effectiveScalerCrop_) * outputToSensor; + outputToSensor = makeTranslate(point2Vec2d(effectiveOffset_)) * outputToSensor; + + Matrix3x3 sensorToInput = makeTransform(effectiveScalerCrop_, localScalerCrop); + + /* + * For every output tile, calculate the position of the corners in the + * input image. + */ + std::vector res; + res.reserve(tileCountW * tileCountH); + for (int y = 0; y < tileCountH; y++) { + for (int x = 0; x < tileCountW; x++) { + Vector2d p{ { static_cast(x) * kDw100BlockSize, + static_cast(y) * kDw100BlockSize } }; + p = p.max(0.0).min(Vector2d{ { static_cast(ow), + static_cast(oh) } }); + + p = transformPoint(outputToSensor, p); + + /* + * \todo: Transformations in sensor space to be added + * here. + */ + + p = transformPoint(sensorToInput, p); + + /* Convert to fixed point */ + uint32_t v = static_cast(p.y() * 16) << 16 | + (static_cast(p.x() * 16) & 0xffff); + res.push_back(v); + } + } + + return res; +} + +} /* namespace libcamera */ diff --git a/src/libcamera/converter/meson.build b/src/libcamera/converter/meson.build index fe2dcebb67da..9f59b57c26b9 100644 --- a/src/libcamera/converter/meson.build +++ b/src/libcamera/converter/meson.build @@ -1,6 +1,7 @@ # SPDX-License-Identifier: CC0-1.0 libcamera_internal_sources += files([ + 'converter_dw100_vertexmap.cpp', 'converter_dw100.cpp', 'converter_v4l2_m2m.cpp' ])