diff mupdf-source/thirdparty/zxing-cpp/core/src/RegressionLine.h @ 3:2c135c81b16c

MERGE: upstream PyMuPDF 1.26.4 with MuPDF 1.26.7
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:44:09 +0200
parents b50eed0cc0ef
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mupdf-source/thirdparty/zxing-cpp/core/src/RegressionLine.h	Mon Sep 15 11:44:09 2025 +0200
@@ -0,0 +1,169 @@
+/*
+* Copyright 2020 Axel Waggershauser
+*/
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+#include "Point.h"
+#include "ZXAlgorithms.h"
+
+#include <algorithm>
+#include <cmath>
+#include <vector>
+
+#ifdef PRINT_DEBUG
+#include <cstdio>
+#endif
+
+namespace ZXing {
+
+class RegressionLine
+{
+protected:
+	std::vector<PointF> _points;
+	PointF _directionInward;
+	PointF::value_t a = NAN, b = NAN, c = NAN;
+
+	friend PointF intersect(const RegressionLine& l1, const RegressionLine& l2);
+
+	template<typename T> bool evaluate(const PointT<T>* begin, const PointT<T>* end)
+	{
+		auto mean = Reduce(begin, end, PointF()) / std::distance(begin, end);
+		PointF::value_t sumXX = 0, sumYY = 0, sumXY = 0;
+		for (auto p = begin; p != end; ++p) {
+			auto d = *p - mean;
+			sumXX += d.x * d.x;
+			sumYY += d.y * d.y;
+			sumXY += d.x * d.y;
+		}
+		if (sumYY >= sumXX) {
+			auto l = std::sqrt(sumYY * sumYY + sumXY * sumXY);
+			a = +sumYY / l;
+			b = -sumXY / l;
+		} else {
+			auto l = std::sqrt(sumXX * sumXX + sumXY * sumXY);
+			a = +sumXY / l;
+			b = -sumXX / l;
+		}
+		if (dot(_directionInward, normal()) < 0) {
+			a = -a;
+			b = -b;
+		}
+		c = dot(normal(), mean); // (a*mean.x + b*mean.y);
+		return dot(_directionInward, normal()) > 0.5f; // angle between original and new direction is at most 60 degree
+	}
+
+	template <typename T> bool evaluate(const std::vector<PointT<T>>& points) { return evaluate(&points.front(), &points.back() + 1); }
+
+	template <typename T> static auto distance(PointT<T> a, PointT<T> b) { return ZXing::distance(a, b); }
+
+public:
+	RegressionLine() { _points.reserve(16); } // arbitrary but plausible start size (tiny performance improvement)
+
+	template<typename T> RegressionLine(PointT<T> a, PointT<T> b)
+	{
+		evaluate(std::vector{a, b});
+	}
+
+	template<typename T> RegressionLine(const PointT<T>* b, const PointT<T>* e)
+	{
+		evaluate(b, e);
+	}
+
+	const auto& points() const { return _points; }
+	int length() const { return _points.size() >= 2 ? int(distance(_points.front(), _points.back())) : 0; }
+	bool isValid() const { return !std::isnan(a); }
+	PointF normal() const { return isValid() ? PointF(a, b) : _directionInward; }
+	auto signedDistance(PointF p) const { return dot(normal(), p) - c; }
+	template <typename T> auto distance(PointT<T> p) const { return std::abs(signedDistance(PointF(p))); }
+	PointF project(PointF p) const { return p - signedDistance(p) * normal(); }
+	PointF centroid() const { return Reduce(_points) / _points.size(); }
+
+	void reset()
+	{
+		_points.clear();
+		_directionInward = {};
+		a = b = c = NAN;
+	}
+
+	void add(PointF p) {
+		assert(_directionInward != PointF());
+		_points.push_back(p);
+		if (_points.size() == 1)
+			c = dot(normal(), p);
+	}
+
+	void pop_back() { _points.pop_back(); }
+	void pop_front()
+	{
+		std::rotate(_points.begin(), _points.begin() + 1, _points.end());
+		_points.pop_back();
+	}
+
+	void setDirectionInward(PointF d) { _directionInward = normalized(d); }
+
+	bool evaluate(double maxSignedDist = -1, bool updatePoints = false)
+	{
+		bool ret = evaluate(_points);
+		if (maxSignedDist > 0) {
+			auto points = _points;
+			while (true) {
+				auto old_points_size = points.size();
+				// remove points that are further 'inside' than maxSignedDist or further 'outside' than 2 x maxSignedDist
+#ifdef __cpp_lib_erase_if
+				std::erase_if(points, [this, maxSignedDist](auto p) {
+					auto sd = this->signedDistance(p);
+					return sd > maxSignedDist || sd < -2 * maxSignedDist;
+				});
+#else
+				auto end = std::remove_if(points.begin(), points.end(), [this, maxSignedDist](auto p) {
+					auto sd = this->signedDistance(p);
+					return sd > maxSignedDist || sd < -2 * maxSignedDist;
+				});
+				points.erase(end, points.end());
+#endif
+				// if we threw away too many points, something is off with the line to begin with
+				if (points.size() < old_points_size / 2 || points.size() < 2)
+					return false;
+				if (old_points_size == points.size())
+					break;
+#ifdef PRINT_DEBUG
+				printf("removed %zu points -> %zu remaining\n", old_points_size - points.size(), points.size());
+				fflush(stdout);
+#endif
+				ret = evaluate(points);
+			}
+
+			if (updatePoints)
+				_points = std::move(points);
+		}
+		return ret;
+	}
+
+	bool isHighRes() const
+	{
+		PointF min = _points.front(), max = _points.front();
+		for (auto p : _points) {
+			UpdateMinMax(min.x, max.x, p.x);
+			UpdateMinMax(min.y, max.y, p.y);
+		}
+		auto diff  = max - min;
+		auto len   = maxAbsComponent(diff);
+		auto steps = std::min(std::abs(diff.x), std::abs(diff.y));
+		// due to aliasing we get bad extrapolations if the line is short and too close to vertical/horizontal
+		return steps > 2 || len > 50;
+	}
+};
+
+inline PointF intersect(const RegressionLine& l1, const RegressionLine& l2)
+{
+	assert(l1.isValid() && l2.isValid());
+	auto d = l1.a * l2.b - l1.b * l2.a;
+	auto x = (l1.c * l2.b - l1.b * l2.c) / d;
+	auto y = (l1.a * l2.c - l1.c * l2.a) / d;
+	return {x, y};
+}
+
+} // ZXing
+