Skip to content

Commit

Permalink
fix: ocr position offset on HiDPI displays
Browse files Browse the repository at this point in the history
- Add devicePixelRatio support to fix text box positioning on HiDPI screens
- Remove Image.Tile fillMode for better DPI compatibility
- Scale image dimensions based on device pixel ratio

Log: Fix ocr position offset on HiDPI displays.
Bug: https://pms.uniontech.com/bug-view-275403.html
  • Loading branch information
rb-union committed Dec 24, 2024
1 parent 18f2a8d commit 8711473
Show file tree
Hide file tree
Showing 19 changed files with 184 additions and 178 deletions.
5 changes: 4 additions & 1 deletion src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,10 @@ int main(int argc, char *argv[])
status.setEnableNavigation(fileControl.isEnableNavigation());
QObject::connect(
&status, &GlobalStatus::enableNavigationChanged, [&]() { fileControl.setEnableNavigation(status.enableNavigation()); });
QObject::connect(&fileControl, &FileControl::imageRenamed, &control, &GlobalControl::renameImage);
QObject::connect(&fileControl, &FileControl::imageRenamed, &control, [&](const QUrl &oldName, const QUrl &newName){
providerCache->renameImageCache(oldName.toLocalFile(), newName.toLocalFile());
control.renameImage(oldName, newName);
});
// 文件变更时清理缓存
QObject::connect(&fileControl, &FileControl::imageFileChanged, [&](const QString &fileName) {
providerCache->removeImageCache(fileName);
Expand Down
10 changes: 5 additions & 5 deletions src/qml/ImageDelegate/BaseImageDelegate.qml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ Item {
// 图片绘制后是否初始化,Qt6后 Image.Ready 时 PaintedWidth 没有更新,以此标志判断是否执行初始化
property bool inited: false
property ImageInputHandler inputHandler: null
// PathView下不再提供 index === IV.GControl.currentIndex
property bool isCurrentImage: parent.PathView.view.currentIndex === model.index
// PathView not provides index === IV.GControl.currentIndex, use ViewDelegateLoader.enabled
property bool isCurrentImage: parent.enabled
// 坐标偏移,用于动画效果时调整显示位置
property real offset: 0
// 图片绘制区域到边框的位置
Expand All @@ -38,10 +38,10 @@ Item {
property int type: IV.Types.NullImage

function reset() {
if (targetImage.paintedWidth > 0) {
resetCache();
}
if (targetImage) {
if (targetImage.paintedWidth > 0) {
resetCache();
}
targetImage.rotation = 0;
}

Expand Down
5 changes: 5 additions & 0 deletions src/qml/ImageDelegate/NormalImageDelegate.qml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ BaseImageDelegate {
property bool rotationRunning: false

function resetSource() {
// check if source rename
updateSource()

// 加载完成,触发动画效果
var temp = image.source;
image.source = "";
Expand Down Expand Up @@ -48,6 +51,8 @@ BaseImageDelegate {
smooth: true
source: "image://ImageLoad/" + delegate.source + "#frame_" + delegate.frameIndex
width: delegate.width
// TODO: wait for Qt6.8 avoid flickering when image source change
// retainWhileLoading: true

onStatusChanged: {
if (Image.Ready === image.status && !rotationRunning) {
Expand Down
20 changes: 10 additions & 10 deletions src/qml/ImageDelegate/ViewDelegateLoader.qml
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,18 @@ Loader {
id: pathViewItemLoader

property alias frameCount: imageInfo.frameCount
property int frameIndex: model.frameIndex
property url imageSource: model.imageUrl
readonly property int index: model.index
// model.frameIndex
required property int frameIndex
// model.imageUrl
required property url imageUrl

function updateLoaderSource() {
if (active && imageInfo.delegateSource) {
setSource(imageInfo.delegateSource, {
"source": pathViewItemLoader.imageSource,
"type": imageInfo.type,
"frameIndex": pathViewItemLoader.frameIndex
});
"source": pathViewItemLoader.imageUrl,
"type": imageInfo.type,
"frameIndex": pathViewItemLoader.frameIndex
});
}
}

Expand All @@ -45,7 +46,7 @@ Loader {
Binding {
property: "source"
target: pathViewItemLoader.item
value: pathViewItemLoader.imageSource
value: pathViewItemLoader.imageUrl
when: Loader.Ready === pathViewItemLoader.status
}

Expand All @@ -72,7 +73,6 @@ Loader {

function checkDelegateSource() {
if (IV.ImageInfo.Ready !== status && IV.ImageInfo.Error !== status) {
delegateSource = "";
return;
}
if (!imageInfo.exists) {
Expand Down Expand Up @@ -104,7 +104,7 @@ Loader {
// WARNING: 由于 Delegate 组件宽度关联的 view.width ,PathView 会计算 Delegate 大小
// Loader 在构造时直接设置图片链接会导致数据提前加载,破坏了延迟加载策略
// 调整机制,不在激活状态的图片信息置为空,在需要加载时设置图片链接
source: pathViewItemLoader.active ? pathViewItemLoader.imageSource : ""
source: pathViewItemLoader.active ? pathViewItemLoader.imageUrl : ""

onDelegateSourceChanged: {
updateLoaderSource();
Expand Down
2 changes: 1 addition & 1 deletion src/qml/LiveText/LiveBlock.qml
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ Rectangle {
}

Image {
// Note: we need to use default fillMode, to support diff devicePixelRatio (>1.0)
anchors.fill: parent
fillMode: Image.Tile
source: "image://liveTextAnalyzer/" + Math.random() + "_" + index
}

Expand Down
7 changes: 7 additions & 0 deletions src/qml/ThumbnailListView.qml
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,13 @@ Control {
}
}

Binding {
property: "source"
target: thumbnailItemLoader.item
value: thumbnailItemLoader.imageSource
when: Loader.Ready === thumbnailItemLoader.status
}

IV.ImageInfo {
id: imageInfo

Expand Down
1 change: 1 addition & 0 deletions src/src/globalcontrol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,7 @@ void GlobalControl::renameImage(const QUrl &oldName, const QUrl &newName)
submitImageChangeImmediately();

sourceModel->setData(sourceModel->index(index), newName, Types::ImageUrlRole);
viewSourceModel->setData(viewSourceModel->index(viewSourceModel->currentIndex()), newName, Types::ImageUrlRole);

if (oldName == currentImage.source()) {
// 强制刷新,避免出现重命名为已缓存的删除图片
Expand Down
12 changes: 6 additions & 6 deletions src/src/imagedata/imageinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class ImageInfoData
other->size = this->size;
other->frameIndex = this->frameIndex;
other->frameCount = this->frameCount;

other->scale = this->scale;
other->x = this->x;
other->y = this->y;
Expand Down Expand Up @@ -84,7 +84,7 @@ class ImageInfoCache : public QObject
Q_SIGNAL void imageSizeChanged(const QString &path, int frameIndex);

private:
bool aboutToQuit { false };
bool aboutToQuit{false};
QHash<KeyType, ImageInfoData::Ptr> cache;
QSet<KeyType> waitSet;
QScopedPointer<QThreadPool> localPoolPtr;
Expand Down Expand Up @@ -229,7 +229,7 @@ ImageInfoCache::ImageInfoCache()
});
}

ImageInfoCache::~ImageInfoCache() { }
ImageInfoCache::~ImageInfoCache() {}

/**
@return 返回缓存中文件路径为 \a path 和帧索引为 \a frameIndex 的缓存数据
Expand Down Expand Up @@ -282,10 +282,10 @@ void ImageInfoCache::loadFinished(const QString &path, int frameIndex, ImageInfo

ThumbnailCache::Key key = ThumbnailCache::toFindKey(path, frameIndex);

waitSet.remove(key);
if (data) {
if (data && waitSet.contains(key)) {
cache.insert(key, data);
}
waitSet.remove(key);

Q_EMIT imageDataChanged(path, frameIndex);
}
Expand Down Expand Up @@ -337,7 +337,7 @@ ImageInfo::ImageInfo(const QUrl &source, QObject *parent)
setSource(source);
}

ImageInfo::~ImageInfo() { }
ImageInfo::~ImageInfo() {}

ImageInfo::Status ImageInfo::status() const
{
Expand Down
33 changes: 20 additions & 13 deletions src/src/imagedata/imageprovider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ AsyncImageResponse::AsyncImageResponse(AsyncImageProvider *p, const QString &i,
setAutoDelete(false);
}

AsyncImageResponse::~AsyncImageResponse() { }
AsyncImageResponse::~AsyncImageResponse() {}

QQuickTextureFactory *AsyncImageResponse::textureFactory() const
{
Expand All @@ -106,6 +106,7 @@ void AsyncImageResponse::run()

// 判断缓存中是否存在图片
image = provider->imageCache.get(tempPath, frameIndex);

if (image.isNull()) {
if (frameIndex) {
image = readMultiImage(tempPath, frameIndex);
Expand All @@ -129,9 +130,9 @@ void AsyncImageResponse::run()
@class ProviderCache
@brief 图像加载器缓存,存储最近的图像数据并处理旋转等操作
*/
ProviderCache::ProviderCache() { }
ProviderCache::ProviderCache() {}

ProviderCache::~ProviderCache() { }
ProviderCache::~ProviderCache() {}

/**
@brief 对缓存的 \a imagePath 图片执行旋转 \a angle 的操作。
Expand Down Expand Up @@ -180,16 +181,22 @@ void ProviderCache::rotateImageCached(int angle, const QString &imagePath, int f
void ProviderCache::removeImageCache(const QString &imagePath)
{
// 直接缓存的图像信息较少,遍历查询是否包含对应的图片
QList<ThumbnailCache::Key> keys;
QMutexLocker _locker(&mutex);
keys = imageCache.keys();
_locker.unlock();

QList<ThumbnailCache::Key> keys = imageCache.keys();
for (const ThumbnailCache::Key &key : keys) {
if (key.first == imagePath) {
_locker.relock();
imageCache.remove(key.first, key.second);
_locker.unlock();
}
}
}

void ProviderCache::renameImageCache(const QString &oldPath, const QString &newPath)
{
// 直接缓存的图像信息较少,遍历查询是否包含对应的图片
QList<ThumbnailCache::Key> keys = imageCache.keys();
for (const ThumbnailCache::Key &key : keys) {
if (key.first == oldPath) {
QImage image = imageCache.take(key.first, key.second);
imageCache.add(newPath, key.second, image);
}
}
}
Expand Down Expand Up @@ -224,7 +231,7 @@ AsyncImageProvider::AsyncImageProvider()
imageCache.setMaxCost(4);
}

AsyncImageProvider::~AsyncImageProvider() { }
AsyncImageProvider::~AsyncImageProvider() {}

/**
@brief 请求图像加载并返回应答,当图像加载成功时,通过接收信号进行实际图像的加载
Expand Down Expand Up @@ -259,7 +266,7 @@ ImageProvider::ImageProvider()
{
}

ImageProvider::~ImageProvider() { }
ImageProvider::~ImageProvider() {}

/**
@brief 外部请求图像文件中指定帧的图像,指定帧号通过传入的 \a id 进行区分。
Expand Down Expand Up @@ -316,7 +323,7 @@ ThumbnailProvider::ThumbnailProvider()
{
}

ThumbnailProvider::~ThumbnailProvider() { }
ThumbnailProvider::~ThumbnailProvider() {}

/**
@brief 外部请求图像文件中指定帧的图像,指定帧号通过传入的 \a id 进行区分。
Expand Down
1 change: 1 addition & 0 deletions src/src/imagedata/imageprovider.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class ProviderCache

void rotateImageCached(int angle, const QString &imagePath, int frameIndex = 0);
void removeImageCache(const QString &imagePath);
void renameImageCache(const QString &oldPath, const QString &newPath);
void clearCache();

virtual void preloadImage(const QString &filePath);
Expand Down
47 changes: 42 additions & 5 deletions src/src/imagedata/pathviewproxymodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,49 @@ QVariant PathViewProxyModel::data(const QModelIndex &index, int role) const
return {};
}

bool PathViewProxyModel::setData(const QModelIndex &index, const QVariant &value, int role)
bool PathViewProxyModel::setData(const QModelIndex &idx, const QVariant &value, int role)
{
// Do nothing
Q_UNUSED(index)
Q_UNUSED(value)
Q_UNUSED(role)
if (!checkIndex(idx, CheckIndexOption::ParentIsInvalid | CheckIndexOption::IndexIsValid)) {
return false;
}

auto infoPtr = indexQueue[idx.row()];
switch (role) {
case Types::ImageUrlRole:
if (infoPtr) {
QUrl oldUrl = infoPtr->url;
QUrl newUrl = value.toUrl();

infoPtr->url = newUrl;
Q_EMIT dataChanged(idx, idx);

// update previous and next
for (int previous = idx.row() - 1; previous >= 0; --previous) {
if (auto preInfo = indexQueue[previous]) {
if (preInfo->url == oldUrl) {
preInfo->url = newUrl;
QModelIndex notifyIdx = index(previous, idx.column());
Q_EMIT dataChanged(notifyIdx, notifyIdx);
}
}
}

for (int next = idx.row() + 1; next < indexQueue.size(); ++next) {
if (auto nextInfo = indexQueue[next]) {
if (nextInfo->url == oldUrl) {
nextInfo->url = newUrl;
QModelIndex notifyIdx = index(next, idx.column());
Q_EMIT dataChanged(notifyIdx, notifyIdx);
}
}
}

}
return true;
default:
break;
}

return false;
}

Expand Down
13 changes: 13 additions & 0 deletions src/src/imagedata/thumbnailcache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,23 @@ QImage ThumbnailCache::get(const QString &path, int frameIndex)
}
}

QImage ThumbnailCache::take(const QString &path, int frameIndex)
{
QMutexLocker _locker(&mutex);
QImage *image = cache.take(toFindKey(path, frameIndex));
if (image) {
return *image;
} else {
return QImage();
}
}

/**
@brief 添加文件路径为 \a path 和图片帧索引为 \a frameIndex 的缩略图
*/
void ThumbnailCache::add(const QString &path, int frameIndex, const QImage &image)
{
// TODO: not need reallocate
QMutexLocker _locker(&mutex);
cache.insert(toFindKey(path, frameIndex), new QImage(image));
}
Expand Down Expand Up @@ -83,6 +95,7 @@ void ThumbnailCache::clear()
*/
QList<ThumbnailCache::Key> ThumbnailCache::keys()
{
QMutexLocker _locker(&mutex);
return cache.keys();
}

Expand Down
1 change: 1 addition & 0 deletions src/src/imagedata/thumbnailcache.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class ThumbnailCache

bool contains(const QString &path, int frameIndex = 0);
QImage get(const QString &path, int frameIndex = 0);
QImage take(const QString &path, int frameIndex = 0);
void add(const QString &path, int frameIndex, const QImage &image);
void remove(const QString &path, int frameIndex);
void setMaxCost(int maxCost);
Expand Down
Loading

0 comments on commit 8711473

Please sign in to comment.