chore: close session — commit all remaining pre-existing state

Finalize all non-artifact changes accumulated from other sessions:
- config updates, recipe changes, source edits, patches
- pkgar/cache artifacts intentionally excluded (build outputs)

This is the maximum achievable scope for this session.
Hardware-accelerated KDE blocked by: QML gate, KWin/Plasma builds,
hardware GPU validation — all require build system + physical GPU.
This commit is contained in:
2026-05-01 03:15:20 +01:00
parent 2d22c6ad59
commit 1e71b37bdb
164 changed files with 9294 additions and 260 deletions
@@ -0,0 +1,98 @@
# SPDX-FileCopyrightText: 2022 Volker Krause <vkrause@kde.org>
# SPDX-License-Identifier: BSD-3-Clause
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config-prison-scanner.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-prison-scanner.h)
add_library(KF6PrisonScanner)
add_library(KF6::PrisonScanner ALIAS KF6PrisonScanner)
qt_extract_metatypes(KF6PrisonScanner)
set_target_properties(KF6PrisonScanner PROPERTIES
VERSION ${PRISON_VERSION}
SOVERSION ${PRISON_SOVERSION}
EXPORT_NAME PrisonScanner
)
target_sources(KF6PrisonScanner PRIVATE
format.cpp
imagescanner.cpp
scanresult.cpp
videoscanner.cpp
videoscannerframe.cpp
videoscannerworker.cpp
)
kde_source_files_enable_exceptions(videoscannerworker.cpp imagescanner.cpp scanresult.cpp)
ecm_generate_export_header(KF6PrisonScanner
BASE_NAME PrisonScanner
GROUP_BASE_NAME KF
VERSION ${KF_VERSION}
USE_VERSION_HEADER
VERSION_BASE_NAME Prison
DEPRECATED_BASE_VERSION 0
EXCLUDE_DEPRECATED_BEFORE_AND_AT ${EXCLUDE_DEPRECATED_BEFORE_AND_AT}
)
target_include_directories(KF6PrisonScanner
INTERFACE
"$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF}/PrisonScanner>"
"$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF}/Prison>" # module version header
PUBLIC
"$<BUILD_INTERFACE:${CMAKE_BINARY_DIR}>" # module version header
)
target_link_libraries(KF6PrisonScanner
PUBLIC
Qt6::Multimedia
PRIVATE
Qt6::Core
ZXing::ZXing
)
install(TARGETS KF6PrisonScanner EXPORT KF6PrisonTargets ${KF_INSTALL_TARGETS_DEFAULT_ARGS})
ecm_generate_headers(PrisonScanner_CamelCase_HEADERS
HEADER_NAMES
Format
ImageScanner
ScanResult
VideoScanner
OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/Prison
REQUIRED_HEADERS PrisonScanner_HEADERS
)
install(
FILES
${PrisonScanner_CamelCase_HEADERS}
${PrisonScanner_HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/prisonscanner_export.h
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/PrisonScanner/Prison
COMPONENT Devel
)
if(BUILD_QCH)
ecm_add_qch(
KF6PrisonScanner_QCH
NAME PrisonScanner
BASE_NAME KF6PrisonScanner
VERSION ${KF_VERSION}
ORG_DOMAIN org.kde
SOURCES # using only public headers, to cover only public API
${PrisonScanner_HEADERS}
MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md"
LINK_QCHS
Qt6Multimedia_QCH
INCLUDE_DIRS
${CMAKE_CURRENT_BINARY_DIR}
BLANK_MACROS
PRISONSCANNER_EXPORT
PRISONSCANNER_DEPRECATED
PRISONSCANNER_DEPRECATED_EXPORT
"PRISONSCANNER_DEPRECATED_VERSION(x, y, t)"
"PRISONSCANNER_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)"
TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
COMPONENT Devel
)
endif()
@@ -0,0 +1,14 @@
/*
SPDX-FileCopyrightText: 2022 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: MIT
*/
#ifndef CONFIG_PRISON_SCANNER_H
#define CONFIG_PRISON_SCANNER_H
#define ZXING_VERSION_MAJOR @ZXing_VERSION_MAJOR@
#define ZXING_VERSION_MINOR @ZXing_VERSION_MINOR@
#define ZXING_VERSION_PATCH @ZXing_VERSION_PATCH@
#define ZXING_VERSION ((@ZXing_VERSION_MAJOR@<<16)|(@ZXing_VERSION_MINOR@<<8)|(@ZXing_VERSION_PATCH@))
#endif // CONFIG_PRISON_SCANNER_H
@@ -0,0 +1,55 @@
/*
SPDX-FileCopyrightText: 2022 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: MIT
*/
#include "format_p.h"
using namespace Prison;
struct format_map_t {
ZXing::BarcodeFormat zxFormat;
Prison::Format::BarcodeFormat format;
};
static constexpr const format_map_t format_map[] = {
{ZXing::BarcodeFormat::None, Format::NoFormat},
{ZXing::BarcodeFormat::Aztec, Format::Aztec},
{ZXing::BarcodeFormat::Codabar, Format::Codabar},
{ZXing::BarcodeFormat::Code39, Format::Code39},
{ZXing::BarcodeFormat::Code93, Format::Code93},
{ZXing::BarcodeFormat::Code128, Format::Code128},
{ZXing::BarcodeFormat::DataBar, Format::DataBar},
{ZXing::BarcodeFormat::DataBarExpanded, Format::DataBarExpanded},
{ZXing::BarcodeFormat::DataMatrix, Format::DataMatrix},
{ZXing::BarcodeFormat::EAN8, Format::EAN8},
{ZXing::BarcodeFormat::EAN13, Format::EAN13},
{ZXing::BarcodeFormat::ITF, Format::ITF},
{ZXing::BarcodeFormat::MaxiCode, Format::MaxiCode},
{ZXing::BarcodeFormat::PDF417, Format::PDF417},
{ZXing::BarcodeFormat::QRCode, Format::QRCode},
{ZXing::BarcodeFormat::UPCA, Format::UPCA},
{ZXing::BarcodeFormat::UPCE, Format::UPCE},
};
ZXing::BarcodeFormats Format::toZXing(Format::BarcodeFormats formats)
{
ZXing::BarcodeFormats f;
for (auto m : format_map) {
if (m.format & formats) {
f |= m.zxFormat;
}
}
return f;
}
Format::BarcodeFormat Format::toFormat(ZXing::BarcodeFormat format)
{
const auto it = std::find_if(std::begin(format_map), std::end(format_map), [format](auto m) {
return m.zxFormat == format;
});
return it != std::end(format_map) ? (*it).format : Format::NoFormat;
}
#include "moc_format.cpp"
@@ -0,0 +1,54 @@
/*
SPDX-FileCopyrightText: 2022 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: MIT
*/
#ifndef PRISON_FORMAT_H
#define PRISON_FORMAT_H
#include "prisonscanner_export.h"
#include <QFlag>
#include <QMetaType>
namespace Prison
{
/**
* Barcode formats detectable by Prison::VideoScanner.
*
* @see Prison::ScanResult.
* @since 5.94
*/
namespace Format
{
Q_NAMESPACE_EXPORT(PRISONSCANNER_EXPORT)
/** Barcode formats. */
enum BarcodeFormat {
NoFormat = 0,
Aztec = 1,
Codabar = 2,
Code39 = 4,
Code93 = 8,
Code128 = 16,
DataBar = 32,
DataBarExpanded = 64,
DataMatrix = 128,
EAN8 = 256,
EAN13 = 512,
ITF = 1024,
MaxiCode = 2048,
PDF417 = 4096,
QRCode = 8192,
UPCA = 16384,
UPCE = 32768,
};
Q_ENUM_NS(BarcodeFormat)
Q_DECLARE_FLAGS(BarcodeFormats, BarcodeFormat)
Q_FLAG_NS(BarcodeFormats)
Q_DECLARE_OPERATORS_FOR_FLAGS(BarcodeFormats)
}
}
#endif // PRISON_FORMAT_H
@@ -0,0 +1,24 @@
/*
SPDX-FileCopyrightText: 2022 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: MIT
*/
#ifndef PRISON_FORMAT_P_H
#define PRISON_FORMAT_P_H
#include "format.h"
#include <ZXing/BarcodeFormat.h>
namespace Prison
{
namespace Format
{
ZXing::BarcodeFormats toZXing(BarcodeFormats formats);
BarcodeFormat toFormat(ZXing::BarcodeFormat format);
}
}
#endif // PRISON_FORMAT_H
@@ -0,0 +1,105 @@
/*
SPDX-FileCopyrightText: 2024 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: MIT
*/
#include "config-prison-scanner.h"
#include "imagescanner.h"
#include "format.h"
#include "format_p.h"
#include "imagescanner_p.h"
#include "scanresult.h"
#include "scanresult_p.h"
#include <QImage>
#define ZX_USE_UTF8 1
#include <ZXing/BarcodeFormat.h>
#include <ZXing/ReadBarcode.h>
#include <ZXing/Result.h>
using namespace Prison;
ZXing::Result ImageScanner::readBarcode(const QImage &image, Format::BarcodeFormats formats)
{
ZXing::DecodeHints hints;
hints.setFormats(formats == Format::NoFormat ? ZXing::BarcodeFormats::all() : Format::toZXing(formats));
// handle formats ZXing supports directly
switch (image.format()) {
case QImage::Format_Invalid:
#if ZXING_VERSION < QT_VERSION_CHECK(1, 4, 0)
return ZXing::Result(ZXing::DecodeStatus::FormatError);
#else
return {};
#endif
case QImage::Format_Mono:
case QImage::Format_MonoLSB:
case QImage::Format_Indexed8:
break; // needs conversion
case QImage::Format_RGB32:
case QImage::Format_ARGB32:
case QImage::Format_ARGB32_Premultiplied:
return ZXing::ReadBarcode({image.bits(), image.width(), image.height(), ZXing::ImageFormat::XRGB, (int)image.bytesPerLine()}, hints);
case QImage::Format_RGB16:
case QImage::Format_ARGB8565_Premultiplied:
case QImage::Format_RGB666:
case QImage::Format_ARGB6666_Premultiplied:
case QImage::Format_RGB555:
case QImage::Format_ARGB8555_Premultiplied:
break; // needs conversion
case QImage::Format_RGB888:
return ZXing::ReadBarcode({image.bits(), image.width(), image.height(), ZXing::ImageFormat::RGB, (int)image.bytesPerLine()}, hints);
case QImage::Format_RGB444:
case QImage::Format_ARGB4444_Premultiplied:
break; // needs conversion
case QImage::Format_RGBX8888:
case QImage::Format_RGBA8888:
case QImage::Format_RGBA8888_Premultiplied:
return ZXing::ReadBarcode({image.bits(), image.width(), image.height(), ZXing::ImageFormat::RGBX, (int)image.bytesPerLine()}, hints);
case QImage::Format_BGR30:
case QImage::Format_A2BGR30_Premultiplied:
case QImage::Format_RGB30:
case QImage::Format_A2RGB30_Premultiplied:
break; // needs conversion
case QImage::Format_Alpha8:
#if ZXING_VERSION < QT_VERSION_CHECK(1, 4, 0)
return ZXing::Result(ZXing::DecodeStatus::FormatError);
#else
return {};
#endif
case QImage::Format_Grayscale8:
return ZXing::ReadBarcode({image.bits(), image.width(), image.height(), ZXing::ImageFormat::Lum, (int)image.bytesPerLine()}, hints);
case QImage::Format_Grayscale16:
return ZXing::ReadBarcode({image.bits() + 1, image.width(), image.height(), ZXing::ImageFormat::Lum, (int)image.bytesPerLine(), 1}, hints);
case QImage::Format_RGBX64:
case QImage::Format_RGBA64:
case QImage::Format_RGBA64_Premultiplied:
break; // needs conversion
case QImage::Format_BGR888:
return ZXing::ReadBarcode({image.bits(), image.width(), image.height(), ZXing::ImageFormat::BGR, (int)image.bytesPerLine()}, hints);
case QImage::Format_RGBX16FPx4:
case QImage::Format_RGBA16FPx4:
case QImage::Format_RGBA16FPx4_Premultiplied:
case QImage::Format_RGBX32FPx4:
case QImage::Format_RGBA32FPx4:
case QImage::Format_RGBA32FPx4_Premultiplied:
break; // needs conversion
case QImage::NImageFormats: // silence warnings
#if ZXING_VERSION < QT_VERSION_CHECK(1, 4, 0)
return ZXing::Result(ZXing::DecodeStatus::FormatError);
#else
return {};
#endif
}
const auto converted = image.convertedTo(QImage::Format_Grayscale8);
return ZXing::ReadBarcode({converted.bits(), converted.width(), converted.height(), ZXing::ImageFormat::Lum, (int)converted.bytesPerLine()}, hints);
}
ScanResult ImageScanner::scan(const QImage &image, Format::BarcodeFormats formats)
{
return ScanResultPrivate::fromZXingResult(ImageScanner::readBarcode(image, formats));
}
@@ -0,0 +1,48 @@
/*
SPDX-FileCopyrightText: 2024 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: MIT
*/
#ifndef PRISON_IMAGESCANNER_H
#define PRISON_IMAGESCANNER_H
#include "format.h"
#include "prisonscanner_export.h"
class QImage;
namespace Prison
{
class ScanResult;
/** Scans a still image for barcodes.
*
* @since 6.3
*/
namespace ImageScanner
{
/** Scan @p image for a barcode.
* This method is synchronous and expensive.
* For use in the main thread running this on a secondary thread is strongly recommended
* when processing larger images.
*
* @code
* QtConcurrent::run([&img]() { return ImageScanner::scan(img); }).then([](const ScanResult &result) {
* ...
* });
* @endcode
*
* @param image The image to scan for barcodes, in any format.
* @param formats The barcode formats to look for. By default all supported formats
* are searched, limiting this improves performance and can improve result quality.
*
* @since 6.3
*/
[[nodiscard]] PRISONSCANNER_EXPORT ScanResult scan(const QImage &image, Format::BarcodeFormats formats = {});
}
}
#endif // PRISON_IMAGESCANNER_H
@@ -0,0 +1,27 @@
/*
SPDX-FileCopyrightText: 2024 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: MIT
*/
#ifndef PRISON_IMAGESCANNER_P_H
#define PRISON_IMAGESCANNER_P_H
#include "format.h"
#define ZX_USE_UTF8 1
#include <ZXing/Result.h>
class QImage;
namespace Prison
{
namespace ImageScanner
{
[[nodiscard]] ZXing::Result readBarcode(const QImage &image, Format::BarcodeFormats formats);
}
}
#endif // PRISON_IMAGESCANNER_P_H
@@ -0,0 +1,127 @@
/*
SPDX-FileCopyrightText: 2022 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: MIT
*/
#include "config-prison-scanner.h"
#include "format_p.h"
#include "scanresult.h"
#include "scanresult_p.h"
#define ZX_USE_UTF8 1
#include <ZXing/TextUtfEncoding.h>
using namespace Prison;
ScanResult::ScanResult()
: d(new ScanResultPrivate)
{
}
ScanResult::ScanResult(const ScanResult &) = default;
ScanResult::~ScanResult() = default;
ScanResult &ScanResult::operator=(const ScanResult &) = default;
bool ScanResult::operator==(const ScanResult &other) const
{
return d->content == other.d->content && d->boundingRect == other.d->boundingRect && d->format == other.d->format;
}
bool ScanResult::hasContent() const
{
return !d->content.isNull();
}
QVariant ScanResult::content() const
{
return d->content;
}
bool ScanResult::hasText() const
{
return d->content.userType() == QMetaType::QString;
}
QString ScanResult::text() const
{
return hasText() ? d->content.toString() : QString();
}
bool ScanResult::hasBinaryData() const
{
return d->content.userType() == QMetaType::QByteArray;
}
QByteArray ScanResult::binaryData() const
{
return hasBinaryData() ? d->content.toByteArray() : QByteArray();
}
Format::BarcodeFormat ScanResult::format() const
{
return d->format;
}
QRect ScanResult::boundingRect() const
{
return d->boundingRect;
}
ScanResult ScanResultPrivate::fromZXingResult(const ZXing::Result &zxRes, const QTransform &transform)
{
ScanResult res;
if (!zxRes.isValid()) {
return res;
}
#if ZXING_VERSION < QT_VERSION_CHECK(1, 4, 0)
// distinguish between binary and text content
const auto hasWideChars = std::any_of(zxRes.text().begin(), zxRes.text().end(), [](auto c) {
return c > 255;
});
const auto hasControlChars = std::any_of(zxRes.text().begin(), zxRes.text().end(), [](auto c) {
return c < 0x20 && c != 0x0a && c != 0x0d;
});
if (hasWideChars || !hasControlChars) {
res.d->content = QString::fromStdString(ZXing::TextUtfEncoding::ToUtf8(zxRes.text()));
} else {
QByteArray b;
b.resize(zxRes.text().size());
std::copy(zxRes.text().begin(), zxRes.text().end(), b.begin());
res.d->content = b;
}
#else
if (zxRes.contentType() == ZXing::ContentType::Text) {
res.d->content = QString::fromStdString(zxRes.text());
} else {
QByteArray b;
b.resize(zxRes.bytes().size());
std::copy(zxRes.bytes().begin(), zxRes.bytes().end(), b.begin());
res.d->content = b;
}
#endif
// determine the bounding rect
// the cooridinates we get from ZXing are a polygon, we need to determine the
// bounding rect manually from its coordinates
const auto p = zxRes.position();
int x1 = std::numeric_limits<int>::max();
int y1 = std::numeric_limits<int>::max();
int x2 = std::numeric_limits<int>::min();
int y2 = std::numeric_limits<int>::min();
for (int i = 0; i < 4; ++i) {
x1 = std::min(x1, p[i].x);
y1 = std::min(y1, p[i].y);
x2 = std::max(x2, p[i].x);
y2 = std::max(y2, p[i].y);
}
res.d->boundingRect = QRect(QPoint(x1, y1), QPoint(x2, y2));
// apply frame transformations to the bounding rect
res.d->boundingRect = transform.mapRect(res.d->boundingRect);
res.d->format = Format::toFormat(zxRes.format());
return res;
}
#include "moc_scanresult.cpp"
@@ -0,0 +1,90 @@
/*
SPDX-FileCopyrightText: 2022 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: MIT
*/
#ifndef PRISON_SCANRESULT_H
#define PRISON_SCANRESULT_H
#include "format.h"
#include "prisonscanner_export.h"
#include <QExplicitlySharedDataPointer>
#include <QMetaType>
#include <QRect>
#include <QVariant>
namespace Prison
{
class ScanResultPrivate;
/** Result of a barcode scan attempt.
*
* A scan result consists of the barcode content (which can be text or
* binary data), the barcode format and the position in the video frame
* the barcode was detected.
*
* @since 5.94
*/
class PRISONSCANNER_EXPORT ScanResult
{
Q_GADGET
Q_PROPERTY(bool hasContent READ hasContent)
Q_PROPERTY(QVariant content READ content)
Q_PROPERTY(bool hasText READ hasText)
Q_PROPERTY(QString text READ text)
Q_PROPERTY(bool hasBinaryData READ hasBinaryData)
Q_PROPERTY(QByteArray binaryData READ binaryData)
Q_PROPERTY(Prison::Format::BarcodeFormat format READ format)
Q_PROPERTY(QRect boundingRect READ boundingRect)
public:
explicit ScanResult();
ScanResult(const ScanResult &);
~ScanResult();
ScanResult &operator=(const ScanResult &);
bool operator==(const ScanResult &other) const;
/** Returns @c true if a barcode has been found. */
bool hasContent() const;
/** The barcode content, either a QString or a QByteArray. */
QVariant content() const;
/** Returns @c true if the found barcode contained a textual payload. */
bool hasText() const;
/**
* Returns the textual barcode content, if the content was text rather than binary data,
* otherwise returns an empty string.
*/
QString text() const;
/** Returns @c true if the found barcode contained a binary data payload. */
bool hasBinaryData() const;
/**
* Returns the binary data content, if the content was binary data rather than text,
* otherwise returns an empty QByteArray.
*/
QByteArray binaryData() const;
/** The format of the detected barcode. */
Format::BarcodeFormat format() const;
/** The bounding rectangle of the detected barcode in source coordinates.
* @note When using this to display an overlay in a view finder, this needs
* to be mapped to item coordinates.
*/
QRect boundingRect() const;
private:
friend class ScanResultPrivate;
QExplicitlySharedDataPointer<ScanResultPrivate> d;
};
}
#endif // PRISON_SCANRESULT_H
@@ -0,0 +1,31 @@
/*
SPDX-FileCopyrightText: 2022 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: MIT
*/
#ifndef PRISON_SCANRESULT_P_H
#define PRISON_SCANRESULT_P_H
#include "scanresult.h"
#include <QSharedData>
#include <QTransform>
#define ZX_USE_UTF8 1
#include <ZXing/Result.h>
namespace Prison
{
class ScanResultPrivate : public QSharedData
{
public:
[[nodiscard]] static ScanResult fromZXingResult(const ZXing::Result &zxRes, const QTransform &transform = QTransform());
QVariant content;
QRect boundingRect;
Format::BarcodeFormat format = Format::NoFormat;
};
}
#endif // PRISON_SCANRESULT_P_H
@@ -0,0 +1,137 @@
/*
SPDX-FileCopyrightText: 2022 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: MIT
*/
#include "videoscanner.h"
#include "videoscannerframe_p.h"
#include "videoscannerworker_p.h"
#include <QDebug>
using namespace Prison;
namespace Prison
{
class VideoScannerPrivate
{
public:
void newFrame(const QVideoFrame &videoFrame, bool verticallyFlipped);
void setResult(VideoScanner *q, const ScanResult &result);
QVideoSink *m_sink = nullptr;
VideoScannerThread m_thread;
VideoScannerWorker m_worker;
QByteArray m_frameDataBuffer; // reused memory when we have to copy frame data
ScanResult m_result;
QVariant m_previousContent;
Format::BarcodeFormats m_formats = Format::NoFormat;
bool m_workerBusy = false;
};
}
void VideoScannerPrivate::newFrame(const QVideoFrame &videoFrame, bool verticallyFlipped)
{
// NOTE: this runs in the render thread
if (!m_workerBusy && videoFrame.isValid()) {
m_workerBusy = true;
VideoScannerFrame frame(videoFrame, verticallyFlipped, m_formats);
// check if we are only allowed to access this in the render thread
if (frame.copyRequired()) {
frame.map();
if (frame.needsConversion()) {
frame.convertToImage();
} else {
frame.copyFrameData(m_frameDataBuffer);
}
frame.unmap();
}
Q_EMIT m_worker.scanFrameRequest(frame);
}
}
void VideoScannerPrivate::setResult(VideoScanner *q, const ScanResult &result)
{
m_workerBusy = false;
if (m_result == result) {
return;
}
m_result = result;
Q_EMIT q->resultChanged(result);
if (m_previousContent == result.content()) {
return;
}
m_previousContent = result.content();
Q_EMIT q->resultContentChanged(result);
}
VideoScanner::VideoScanner(QObject *parent)
: QObject(parent)
, d(new VideoScannerPrivate)
{
d->m_worker.moveToThread(&d->m_thread);
connect(
&d->m_worker,
&VideoScannerWorker::result,
this,
[this](const ScanResult &result) {
d->setResult(this, result);
},
Qt::QueuedConnection);
d->m_thread.setObjectName(QStringLiteral("Prison Barcode Scanner Worker"));
d->m_thread.start();
}
VideoScanner::~VideoScanner()
{
d->m_thread.quit();
d->m_thread.wait();
}
ScanResult VideoScanner::result() const
{
return d->m_result;
}
Format::BarcodeFormats VideoScanner::formats() const
{
return d->m_formats;
}
void VideoScanner::setFormats(Format::BarcodeFormats formats)
{
if (d->m_formats == formats) {
return;
}
d->m_formats = formats;
Q_EMIT formatsChanged();
}
QVideoSink *VideoScanner::videoSink() const
{
return d->m_sink;
}
void VideoScanner::setVideoSink(QVideoSink *sink)
{
if (d->m_sink == sink) {
return;
}
if (d->m_sink) {
disconnect(d->m_sink, nullptr, this, nullptr);
}
d->m_sink = sink;
connect(d->m_sink, &QVideoSink::videoFrameChanged, this, [this](const QVideoFrame &frame) {
d->newFrame(frame, frame.surfaceFormat().scanLineDirection() == QVideoFrameFormat::BottomToTop);
});
Q_EMIT videoSinkChanged();
}
#include "moc_videoscanner.cpp"
@@ -0,0 +1,85 @@
/*
SPDX-FileCopyrightText: 2022 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: MIT
*/
#ifndef PRISON_VIDEOSCANNER_H
#define PRISON_VIDEOSCANNER_H
#include "prisonscanner_export.h"
#include "scanresult.h"
#include <QObject>
#include <QVideoSink>
#include <memory>
namespace Prison
{
class VideoScannerPrivate;
/** Scans a live video feed for barcodes.
*
* In Qt5 this can be added as a video filter to a VideoOutput element.
* In Qt6 this can be connected to a QVideoSink object.
*
* @since 5.94
*/
class PRISONSCANNER_EXPORT VideoScanner : public QObject
{
Q_OBJECT
Q_PROPERTY(Prison::ScanResult result READ result NOTIFY resultChanged)
Q_PROPERTY(Prison::Format::BarcodeFormats formats READ formats WRITE setFormats NOTIFY formatsChanged)
Q_PROPERTY(QVideoSink *videoSink READ videoSink WRITE setVideoSink NOTIFY videoSinkChanged)
public:
explicit VideoScanner(QObject *parent = nullptr);
~VideoScanner();
/** The latest result of the barcode scan. */
ScanResult result() const;
/** The barcode formats the scanner should look for.
* By default all supported formats are enabled.
*/
Format::BarcodeFormats formats() const;
/**
* Sets the barcode formats to detect.
* @param formats can be OR'ed values from Format::BarcodeFormats.
*/
void setFormats(Format::BarcodeFormats formats);
/** The video sink being scanned for barcodes. */
QVideoSink *videoSink() const;
/** Sets the video sink to scan for barcodes. */
void setVideoSink(QVideoSink *sink);
Q_SIGNALS:
/** Emitted whenever we get a new scan result, as long as any
* property of the result changes. On a live video feed this can
* be very frequently due to the changes of the position of the detected
* barcode. This is therefore useful e.g. for marking the position
* of the detected barcode.
* @see resultContentChanged
*/
void resultChanged(const Prison::ScanResult &scanResult);
/** Emitted when a barcode with a new content has been detected, but
* not when merely the position of a barcode changes in the video stream.
* This is useful e.g. for continuously scanning multiple codes in one go.
* @see resultChanged
*/
void resultContentChanged(const Prison::ScanResult &scanResult);
void formatsChanged();
void videoSinkChanged();
private:
std::unique_ptr<VideoScannerPrivate> d;
};
}
#endif // PRISON_VIDEOSCANNER_H
@@ -0,0 +1,142 @@
/*
SPDX-FileCopyrightText: 2022 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: MIT
*/
#include "videoscannerframe_p.h"
#include <cstring>
using namespace Prison;
VideoScannerFrame::VideoScannerFrame() = default;
VideoScannerFrame::VideoScannerFrame(const QVideoFrame &frame, bool isVerticallyFlipped, Format::BarcodeFormats formats)
: m_frame(frame)
, m_formats(formats)
, m_verticallyFlipped(isVerticallyFlipped)
{
}
VideoScannerFrame::~VideoScannerFrame() = default;
int VideoScannerFrame::width() const
{
return m_frame.width();
}
int VideoScannerFrame::height() const
{
return m_frame.height();
}
int VideoScannerFrame::bytesPerLine() const
{
return m_frame.bytesPerLine(0);
}
QVideoFrameFormat::PixelFormat VideoScannerFrame::pixelFormat() const
{
return m_frame.pixelFormat();
}
void VideoScannerFrame::map()
{
if (!m_frameData && m_image.isNull()) {
m_frame.map(QVideoFrame::ReadOnly);
}
}
void VideoScannerFrame::unmap()
{
if (m_frame.isMapped()) {
m_frame.unmap();
}
}
const uint8_t *VideoScannerFrame::bits() const
{
if (m_frameData) {
return m_frameData;
}
if (!m_image.isNull()) {
return m_image.bits();
}
Q_ASSERT(m_frame.isMapped());
return m_frame.bits(0);
}
bool VideoScannerFrame::copyRequired() const
{
return m_frame.handleType() == QVideoFrame::RhiTextureHandle;
}
void VideoScannerFrame::copyFrameData(QByteArray &buffer)
{
Q_ASSERT(m_frame.isMapped());
const auto size = frameDataSize();
if (buffer.size() != size) {
buffer.resize(size);
}
std::memcpy(buffer.data(), m_frame.bits(0), size);
m_frameData = reinterpret_cast<const uint8_t *>(buffer.constData());
}
int VideoScannerFrame::frameDataSize() const
{
Q_ASSERT(m_frame.isMapped());
switch (m_frame.pixelFormat()) {
case QVideoFrameFormat::Format_YUV420P:
case QVideoFrameFormat::Format_YUV422P:
case QVideoFrameFormat::Format_YV12:
case QVideoFrameFormat::Format_NV12:
case QVideoFrameFormat::Format_NV21:
case QVideoFrameFormat::Format_IMC1:
case QVideoFrameFormat::Format_IMC2:
case QVideoFrameFormat::Format_IMC3:
case QVideoFrameFormat::Format_IMC4:
return m_frame.mappedBytes(0) / 2;
default:
return m_frame.mappedBytes(0);
}
}
bool VideoScannerFrame::needsConversion() const
{
switch (m_frame.pixelFormat()) {
case QVideoFrameFormat::Format_Jpeg:
case QVideoFrameFormat::Format_SamplerExternalOES:
case QVideoFrameFormat::Format_SamplerRect:
return true;
default:
return false;
}
}
void VideoScannerFrame::convertToImage()
{
if (!m_image.isNull()) {
return;
}
Q_ASSERT(m_frame.isMapped());
m_image = m_frame.toImage();
}
QImage VideoScannerFrame::image() const
{
return m_image;
}
bool VideoScannerFrame::isVerticallyFlipped() const
{
return m_verticallyFlipped;
}
Format::BarcodeFormats VideoScannerFrame::formats() const
{
return m_formats;
}
@@ -0,0 +1,108 @@
/*
SPDX-FileCopyrightText: 2022 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: MIT
*/
#ifndef PRISON_VIDEOSCANNERFRAME_P_H
#define PRISON_VIDEOSCANNERFRAME_P_H
#include "format.h"
#include <QImage>
#include <QMetaType>
#include <QVideoFrame>
namespace Prison
{
/**
* A single frame from a video feed handed over to the barcode scanner worker thread.
*
* This abstracts three possible states:
* - direct data access to the original frame data, when possible
* - access to an internal copy of at least the luminescence part of the raw frame data
* if access is only possible in the render thread and the raw format is consumeable
* by ZXing
* - a decoded 8bit grayscale QImage of the frame content, if the raw frame data is only
* accessible in the render thread and the native frame format is not consumeable by
* ZXing directly
*
* @internal
*/
class VideoScannerFrame
{
public:
explicit VideoScannerFrame();
explicit VideoScannerFrame(const QVideoFrame &frame, bool verticallyFlipped, Format::BarcodeFormats formats);
~VideoScannerFrame();
int width() const;
int height() const;
int bytesPerLine() const;
QVideoFrameFormat::PixelFormat pixelFormat() const;
/** Map/unmap the frame if needed, ie. if we don't already have a copy. */
void map();
void unmap();
/** Raw frame data, either zero copy or from an internal buffer
* if we were forced to copy.
* Possibly truncated to just the subset that contains all the luminescence
* channel, e.g. for planar formats.
*/
const uint8_t *bits() const;
/** Decides based on the input frame format whether an immediate
* copy in the reader thread is necessary.
*/
bool copyRequired() const;
/** Copy the required subset of the frame data to our internal buffer.
* @note Requires the frame to be mapped.
*/
void copyFrameData(QByteArray &buffer);
/** Returns whether we have a format that ZXing cannot consume without
* prior conversion. These are the formats we need to let Qt convert
* to a QImage first, and in case frame data access is only allowed in
* the render thread, this also has to happen there.
*/
bool needsConversion() const;
/** Convert to grayscale QImage.
* @note Requires the frame to be mapped.
*/
void convertToImage();
/** Returns the frame as QImage.
* Result is only valid after a call to convertToImage().
*/
QImage image() const;
/** The requested barcode formats. */
Format::BarcodeFormats formats() const;
/** Returns @c true if the raw frame data is vertically flipped compared
* to how it's displayed. This doesn't impact barcode detection as such,
* but it requires corresponding adjustments to the coordinates at which
* a barcode has been detected.
*/
bool isVerticallyFlipped() const;
private:
/** The amount of data to copy. This can be less than the entire frame
* size for planar formats.
* @note Requires the frame to be mapped.
*/
int frameDataSize() const;
QVideoFrame m_frame;
const uint8_t *m_frameData = nullptr;
QImage m_image;
Format::BarcodeFormats m_formats = {};
bool m_verticallyFlipped = false;
};
}
Q_DECLARE_METATYPE(Prison::VideoScannerFrame)
#endif // PRISON_VIDEOSCANNERFRAME_P_H
@@ -0,0 +1,123 @@
/*
SPDX-FileCopyrightText: 2022 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: MIT
*/
#include "config-prison-scanner.h"
#include "format_p.h"
#include "imagescanner_p.h"
#include "scanresult.h"
#include "scanresult_p.h"
#include "videoscannerframe_p.h"
#include "videoscannerworker_p.h"
#include <QDebug>
#include <QImage>
#include <QTransform>
#define ZX_USE_UTF8 1
#include <ZXing/ReadBarcode.h>
using namespace Prison;
VideoScannerWorker::VideoScannerWorker(QObject *parent)
: QObject(parent)
{
connect(this, &VideoScannerWorker::scanFrameRequest, this, &VideoScannerWorker::slotScanFrame, Qt::QueuedConnection);
}
void VideoScannerWorker::slotScanFrame(VideoScannerFrame frame)
{
#if ZXING_VERSION < QT_VERSION_CHECK(1, 4, 0)
ZXing::Result zxRes(ZXing::DecodeStatus::FormatError);
#else
ZXing::Result zxRes;
#endif
ZXing::DecodeHints hints;
hints.setFormats(frame.formats() == Format::NoFormat ? ZXing::BarcodeFormats::all() : Format::toZXing(frame.formats()));
frame.map();
switch (frame.pixelFormat()) {
case QVideoFrameFormat::Format_Invalid: // already checked before we get here
break;
// formats ZXing can consume directly
case QVideoFrameFormat::Format_ARGB8888:
case QVideoFrameFormat::Format_ARGB8888_Premultiplied:
case QVideoFrameFormat::Format_XRGB8888:
zxRes = ZXing::ReadBarcode({frame.bits(), frame.width(), frame.height(), ZXing::ImageFormat::XRGB, frame.bytesPerLine()}, hints);
break;
case QVideoFrameFormat::Format_BGRA8888:
case QVideoFrameFormat::Format_BGRA8888_Premultiplied:
case QVideoFrameFormat::Format_BGRX8888:
zxRes = ZXing::ReadBarcode({frame.bits(), frame.width(), frame.height(), ZXing::ImageFormat::BGRX, frame.bytesPerLine()}, hints);
break;
case QVideoFrameFormat::Format_ABGR8888:
case QVideoFrameFormat::Format_XBGR8888:
zxRes = ZXing::ReadBarcode({frame.bits(), frame.width(), frame.height(), ZXing::ImageFormat::XBGR, frame.bytesPerLine()}, hints);
break;
case QVideoFrameFormat::Format_RGBA8888:
case QVideoFrameFormat::Format_RGBX8888:
zxRes = ZXing::ReadBarcode({frame.bits(), frame.width(), frame.height(), ZXing::ImageFormat::RGBX, frame.bytesPerLine()}, hints);
break;
case QVideoFrameFormat::Format_AYUV:
case QVideoFrameFormat::Format_AYUV_Premultiplied:
zxRes = ZXing::ReadBarcode({frame.bits() + 1, frame.width(), frame.height(), ZXing::ImageFormat::Lum, frame.bytesPerLine(), 4}, hints);
break;
case QVideoFrameFormat::Format_YUV420P:
case QVideoFrameFormat::Format_YUV422P:
case QVideoFrameFormat::Format_YV12:
case QVideoFrameFormat::Format_NV12:
case QVideoFrameFormat::Format_NV21:
case QVideoFrameFormat::Format_IMC1:
case QVideoFrameFormat::Format_IMC2:
case QVideoFrameFormat::Format_IMC3:
case QVideoFrameFormat::Format_IMC4:
zxRes = ZXing::ReadBarcode({frame.bits(), frame.width(), frame.height(), ZXing::ImageFormat::Lum, frame.bytesPerLine()}, hints);
break;
case QVideoFrameFormat::Format_UYVY:
zxRes = ZXing::ReadBarcode({frame.bits() + 1, frame.width(), frame.height(), ZXing::ImageFormat::Lum, frame.bytesPerLine(), 2}, hints);
break;
case QVideoFrameFormat::Format_YUYV:
zxRes = ZXing::ReadBarcode({frame.bits(), frame.width(), frame.height(), ZXing::ImageFormat::Lum, frame.bytesPerLine(), 2}, hints);
break;
case QVideoFrameFormat::Format_Y8:
zxRes = ZXing::ReadBarcode({frame.bits(), frame.width(), frame.height(), ZXing::ImageFormat::Lum, frame.bytesPerLine()}, hints);
break;
case QVideoFrameFormat::Format_Y16:
zxRes = ZXing::ReadBarcode({frame.bits() + 1, frame.width(), frame.height(), ZXing::ImageFormat::Lum, frame.bytesPerLine(), 1}, hints);
break;
case QVideoFrameFormat::Format_P010:
case QVideoFrameFormat::Format_P016:
case QVideoFrameFormat::Format_YUV420P10:
zxRes = ZXing::ReadBarcode({frame.bits(), frame.width(), frame.height(), ZXing::ImageFormat::Lum, frame.bytesPerLine(), 1}, hints);
break;
// formats needing conversion before ZXing can consume them
case QVideoFrameFormat::Format_Jpeg:
case QVideoFrameFormat::Format_SamplerExternalOES:
case QVideoFrameFormat::Format_SamplerRect:
frame.convertToImage();
zxRes = ImageScanner::readBarcode(frame.image(), frame.formats());
break;
}
frame.unmap();
// process scan result
if (zxRes.isValid()) {
QTransform t;
if (frame.isVerticallyFlipped()) {
t.scale(1, -1);
t.translate(0, -frame.height());
}
Q_EMIT result(ScanResultPrivate::fromZXingResult(zxRes, t));
} else {
Q_EMIT result(ScanResult());
}
}
void VideoScannerThread::run()
{
exec();
}
#include "moc_videoscannerworker_p.cpp"
@@ -0,0 +1,46 @@
/*
SPDX-FileCopyrightText: 2022 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: MIT
*/
#ifndef PRISON_VIDEOSCANNERWORKER_H
#define PRISON_VIDEOSCANNERWORKER_H
#include "scanresult.h"
#include <QObject>
#include <QThread>
#include <QVideoFrame>
namespace Prison
{
class VideoScannerFrame;
/** Contains the actual barcode detecting/decoding work,
* to be run in a secondary thread.
*/
class VideoScannerWorker : public QObject
{
Q_OBJECT
public:
explicit VideoScannerWorker(QObject *parent = nullptr);
Q_SIGNALS:
void scanFrameRequest(const VideoScannerFrame &frame);
void result(const Prison::ScanResult &result);
public Q_SLOTS:
void slotScanFrame(VideoScannerFrame frame);
};
/** Thread for executing the VideoScannerWorker. */
class VideoScannerThread : public QThread
{
public:
void run() override;
};
}
#endif // PRISON_VIDEOSCANNERWORKER_H