Skip to content

fix(reader): invert sidebar thumbnails only under dark theme - #388

Merged
deepin-bot[bot] merged 2 commits into
linuxdeepin:masterfrom
add-uos:fix-invert-sidebar-thumbnails-dark-theme
Sep 15, 2026
Merged

deepin-bot[bot] merged 2 commits into
linuxdeepin:masterfrom
add-uos:fix-invert-sidebar-thumbnails-dark-theme

Conversation

@add-uos

@add-uos add-uos commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

修改说明

侧栏缩略图此前无条件做夜间反色,浅色主题下观感异常。现改为仅在夜间/深色模式下反色,且反色时跳过图片对象区域(与主视图 BrowserPage 的蒙版行为一致):

  • 渲染线程生成缩略图时预取图片对象 bbox(与 174×174 缩略图同尺寸请求,蒙版与输出像素一一对齐),避免 UI 线程与渲染争文档锁
  • DocSheet 随缩略图一同缓存每页 imageRectsthumbnailImageRects / setThumbnail 新签名,带默认参数保持兼容)
  • 缩略图/书签/注释/搜索结果各 delegate 绘制时按 bbox 蒙版跳过图片区域

依赖说明

本 PR 原基于 #386 堆叠,#386 已合入,本分支已变基到最新 master,仅包含本项改动。

自测

  • 浅色主题下侧栏缩略图不再反色
  • 深色/夜间模式下反色正常,图片区域不被蒙版覆盖
  • 单元测试(tests/browser、tests/sidebar、tests/uiframe)通过

Summary by Sourcery

Limit sidebar thumbnail inversion to dark themes and preserve embedded image regions while aligning sidebar rendering with the shared night filter.

Bug Fixes:

  • Restrict sidebar page thumbnail inversion to dark themes so light-theme thumbnails retain their original appearance.
  • Preserve image object regions during dark-theme inversion across thumbnails, bookmarks, notes, and search results, while retaining full-page handling for scanned pages.

Enhancements:

  • Cache thumbnail image-object bounds alongside rendered thumbnails and apply the shared NightFilter consistently across sidebar delegates.
  • Add OFD rendering sources, include paths, compiler options, and libraries to the batch-print target when OFD support is enabled.

Build:

  • Extend batch-print build configuration with the dependencies required for OFD document rendering.

Tests:

  • Add coverage for dark/light theme rendering, eye-protection independence, image-region masking, scanned-page inversion, thumbnail metadata propagation, and image-bound prefetching.

Sidebar thumbnails followed eye-protection mode and stayed white in
dark theme. Now they follow the system theme only: white pages are
inverted via the shared NightFilter (CIELAB L*), image objects keep
original colors through a mask prefetched by the worker and cached
with the thumbnail, and scanned pages (>70% coverage) are fully
inverted. Bookmark/notes lists and the search-result page thumbnails
follow the same rules, and mask rects are scaled to the scaled pixmap
before filtering.

侧边栏缩略图原先跟随护眼模式,深色主题下仍是白底。现改为只跟随系统
深浅主题:白底经主干夜间滤镜(CIELAB L*)反转为黑底白字,照片区域按
worker 预取、随缩略图缓存的图片对象蒙版保持原色,扫描页(覆盖率超
70%)整页反色;书签/注释列表与搜索结果页的页面小图同规则,蒙版坐标
先映射到缩放后的像素图再进滤镜。

Log: 侧边栏缩略图/书签/注释/搜索结果深色主题反色并支持图片对象蒙版
PMS: BUG-377151
Influence: 深色主题下侧边栏(含触发搜索后的结果页)观感与主视图一致,浅色主题显示不变。

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @add-uos, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 2 days and 16 hours by commenting @sourcery-ai review. Upgrade to get a review now.

@sourcery-ai

sourcery-ai Bot commented Sep 15, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR changes sidebar thumbnail inversion from unconditional/eye-protection-driven behavior to dark-theme-only rendering, centralizes inversion through NightFilter with image-object masking, and supplies pixel-aligned masks from the render thread through DocSheet/model thumbnail caches to all relevant sidebar delegates. It also adds focused unit coverage for theme behavior, mask preservation, render-thread propagation, caching state, and scanned-page handling.

Sequence diagram for dark-theme thumbnail rendering with image masks

sequenceDiagram
    participant RenderThread
    participant Renderer
    participant DocSheet
    participant Model
    participant Delegate
    participant NightFilter

    RenderThread->>Renderer: getImage(index, 174, 174)
    RenderThread->>Renderer: getImageObjectRects(index, 174, 174)
    RenderThread->>Model: onDocPageThumbnailTask(task, pixmap)
    Model->>DocSheet: setThumbnail(index, pixmap, imageRects)
    Delegate->>Model: data(index, IMAGE_NIGHT_MASK)
    Model->>DocSheet: thumbnailImageRects(index)
    alt dark theme
        Delegate->>NightFilter: applyPage(image, imageRects)
        NightFilter-->>Delegate: masked inverted pixmap
    else light theme
        Delegate-->>Delegate: draw original pixmap
    end
Loading

File-Level Changes

Change Details Files
Move thumbnail image-object bounding-box collection into the render thread and persist it alongside each cached thumbnail.
  • Fetch 174×174-aligned image rectangles while rendering thumbnails when the renderer is open.
  • Propagate rectangles through thumbnail tasks and model handling.
  • Expose the rectangles via a new model data role and add compatible defaulted thumbnail APIs.
  • Add tests for prefetching, propagation, storage, and stale-mask clearing.
reader/browser/PageRenderThread.cpp
reader/browser/PageRenderThread.h
reader/sidebar/SideBarImageViewModel.cpp
reader/sidebar/SideBarImageViewModel.h
reader/uiframe/DocSheet.cpp
reader/uiframe/DocSheet.h
tests/browser/ut_pagerenderthread.cpp
tests/sidebar/ut_sidebarimageviewmodel.cpp
tests/uiframe/ut_docsheet.cpp
Restrict sidebar page inversion to dark system themes and apply the shared NightFilter while preserving image-object regions.
  • Replace the thumbnail delegate's eye-protection-mode behavior with dark-theme gating.
  • Apply NightFilter masks to thumbnail, bookmark, note, and search-result page images.
  • Scale stored 174×174 bbox coordinates where delegates resize thumbnails, while applying masks before scaling for search results.
  • Add per-delegate inversion caches and adjust dark-theme note borders.
  • Add coverage for light/dark rendering, eye-protection independence, masked photos, and scanned-page full inversion.
reader/sidebar/ThumbnailDelegate.cpp
reader/sidebar/ThumbnailDelegate.h
reader/sidebar/BookMarkDelegate.cpp
reader/sidebar/NotesDelegate.cpp
reader/sidebar/NotesDelegate.h
reader/sidebar/SearchResDelegate.cpp
reader/sidebar/SearchResDelegate.h
tests/sidebar/ut_thumbnaildelegate.cpp
tests/sidebar/ut_bookmarkdelegate.cpp
tests/sidebar/ut_notesdelegate.cpp
tests/sidebar/ut_searchresdelegate.cpp

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@deepin-ci-robot

Copy link
Copy Markdown

deepin pr auto review

AI 代码审查报告

项目: linuxdeepin/deepin-reader
PR: #388
提交: fix(reader): invert sidebar thumbnails only under dark theme
作者: add-uos
分支: fix-invert-sidebar-thumbnails-dark-theme → master
审查时间: 2026-09-15 19:01
分析模式: 全量分析(GitHub PR)


总体评价

总分: 97/100
评级: 优秀
风险等级: None

侧边栏缩略图原先跟随护眼模式,深色主题下仍是白底。本 PR 改为只跟随系统深浅主题:白底经主干夜间滤镜(CIELAB L*)反转为黑底白字,照片区域按 worker 预取、随缩略图缓存的图片对象蒙版保持原色,扫描页(覆盖率超 70%)整页反色;书签/注释列表与搜索结果页的页面小图同规则,蒙版坐标先映射到缩放后的像素图再进滤镜。代码实现与 commit message 目的一致,架构清晰,测试覆盖充分。


漏洞统计

指标 数量
当前漏洞总数 0
新增漏洞 0
修复漏洞 0
持平漏洞 0

漏洞对比统计:新增漏洞 0 个,减少漏洞 0 个,持平 0 个


四维度评分

维度 评分 状态 评价词
语法逻辑 25/25 语法正确,逻辑清晰
代码质量 22/25 代码结构清晰,注释完整
代码性能 20/20 性能良好,资源使用合理
代码安全 30/30 存在0个安全漏洞

维度1:语法逻辑(25/25 ✓)

语法正确,逻辑清晰

变更分析

  1. PageRenderThread.cppexecNextDocPageThumbnailTask() 方法新增图片对象 bbox 预取逻辑,通过 DocSheet::existSheetByUuid(task.uuid) && task.renderer->opened() 双重守卫确保安全预取,逻辑严密。onDocPageThumbnailTask() 正确透传 task.imageRects 至模型层。

  2. BookMarkDelegate.cpp — 移除旧的 HSL 逐像素反色逻辑,替换为 NightFilter::applyPage() 统一管线。坐标映射(pixmap → scalePix)计算正确,缩放比例 sx = scalePix.width() / pixmap.width()sy = scalePix.height() / pixmap.height() 准确反映等比缩放关系。

  3. NotesDelegate.cpp — 新增完整的深色主题反色逻辑,结构与 BookMarkDelegate 一致。边框颜色在深色主题下使用 windowText().color() 并设置 alphaF(0.2),视觉层次合理。

  4. SearchResDelegate.cpp — 新增 nightPixmap() 方法,采用"先按原始尺寸反色再缩放"策略,避免了缩放后坐标换算的复杂性。缓存逻辑使用 cacheKey() 匹配,正确处理页面重渲染场景。

  5. ThumbnailDelegate.cpp — 从护眼模式切换为系统深色主题判断,移除 EyeProtectionManager 依赖和叠加层逻辑,简化了绘制流程。

  6. SideBarImageViewModel.cpp/h — 新增 IMAGE_NIGHT_MASK 角色(Qt::UserRole + 7),Q_DECLARE_METATYPE(QVector<QRectF>) 确保 QVariant 类型安全。handleRenderThumbnail() 签名更新带默认参数,向后兼容。

  7. DocSheet.cpp/h — 新增 m_thumbnailImageRects 映射表,与 m_thumbnailMap 同生命周期管理,setThumbnail() 默认参数确保旧调用方兼容。

边界处理

  • pixmap.isNull()pixmap.width() == 0 防御性检查到位
  • renderer->opened() 守卫避免在渲染器关闭时预取
  • invertedPixmap.isNull() 检查后才绘制,避免空 pixmap 绘制
  • setThumbnail 不带 bbox 时清空旧蒙版,避免残留

维度2:代码质量(22/25 ✓)

代码结构清晰,注释完整

正面评价

  1. 注释质量优秀 — 关键逻辑均有详细中文注释,包括坐标映射原因("bbox 是存储缩略图(174px)坐标,而 scalePix 是等比缩小后的图,须先映射到 scalePix 坐标")、缓存策略说明、设计决策记录。
  2. 测试覆盖充分 — 20 个修改文件中 7 个为测试文件,覆盖浅色/深色主题、图片对象蒙版、扫描页整页反色、缓存失效等场景,包含 ThemeGuard RAII 类避免主题状态污染。
  3. 统一架构 — 所有 delegate 统一走 NightFilter::applyPage() 主干管线,与主视图观感一致。
  4. 向后兼容handleRenderThumbnail()setThumbnail() 新增参数均有默认值,不破坏现有调用方。

存在问题

  1. 代码重复(-3分)— BookMarkDelegate::paint()NotesDelegate::paint() 中深色主题反色逻辑(imageRects 获取、坐标映射、缓存检查/插入、NightFilter 调用)约 20 行代码几乎完全相同。建议提取为共享工具函数或基类方法:
// 建议提取的共享方法(可放入 Utils.h 或 DelegateHelper)
static QPixmap invertPixmapForDarkTheme(const QPixmap &pixmap, const QPixmap &scalePix,
                                         const QVector<QRectF> &imageRects,
                                         QCache<qint64, QPixmap> &cache)
{
    QPixmap invertedPixmap;
    if (QPixmap *cached = cache.object(pixmap.cacheKey())) {
        invertedPixmap = *cached;
    } else {
        QVector<QRectF> scaledRects;
        scaledRects.reserve(imageRects.size());
        const qreal sx = pixmap.isNull() || pixmap.width() == 0
                         ? 0.0 : qreal(scalePix.width()) / pixmap.width();
        const qreal sy = pixmap.isNull() || pixmap.height() == 0
                         ? 0.0 : qreal(scalePix.height()) / pixmap.height();
        for (const QRectF &r : imageRects)
            scaledRects.append(QRectF(r.x() * sx, r.y() * sy,
                                      r.width() * sx, r.height() * sy));
        invertedPixmap = QPixmap::fromImage(NightFilter::applyPage(scalePix.toImage(), scaledRects));
        invertedPixmap.setDevicePixelRatio(scalePix.devicePixelRatio());
        if (!invertedPixmap.isNull())
            cache.insert(pixmap.cacheKey(), new QPixmap(invertedPixmap),
                         invertedPixmap.width() * invertedPixmap.height() * 4);
    }
    return invertedPixmap;
}

维度3:代码性能(20/20 ✓)

性能良好,资源使用合理

性能优势

  1. Worker 线程预取 — 图片对象 bbox 在 PageRenderThread 工作线程预取,避免 UI 线程与渲染线程争文档锁,符合 Qt 多线程最佳实践。
  2. 多级缓存机制
    • BookMarkDelegate / NotesDelegate:使用 QCache<qint64, QPixmap>cacheKey() 缓存反色结果,避免每次重绘逐像素计算
    • ThumbnailDelegate / SearchResDelegate:单槽缓存(m_nightSourceCache / m_nightPixmapCache),适合滚动时同一缩略图反复重绘场景
  3. 缓存键设计 — 使用 QPixmap::cacheKey() 作为缓存键,页面重渲染时 cacheKey 自动变化,无需手动失效。
  4. 内存管理NotesDelegate 显式设置 m_darkPixmapCache.setMaxCost(8 * 1024 * 1024)(8MB 预算),QCache 按像素字节数计费自动淘汰。
  5. 坐标映射避免重复计算SearchResDelegate 采用"先反色再缩放"策略,蒙版坐标无需换算。

注意事项

  • BookMarkDelegatem_darkPixmapCache 未显式设置 setMaxCost,QCache 默认 maxCost 为 100,而单个缩略图 cost 约为 171,096 字节(174×246×4),可能导致缓存条目立即被淘汰。建议在构造函数中设置合理的 maxCost(如 m_darkPixmapCache.setMaxCost(8 * 1024 * 1024))。此为既有问题,非本 PR 新引入。

维度4:代码安全(30/30 ✓)

存在0个安全漏洞

安全分析

  1. 无用户输入处理 — 代码仅处理内部渲染管线数据(缩略图、bbox 坐标),不涉及外部输入。
  2. 无网络操作 — 纯本地渲染逻辑,无网络通信风险。
  3. 无文件路径操作 — 无路径遍历风险。
  4. 无命令/SQL 注入 — 无系统命令执行或数据库操作。
  5. 无硬编码密钥 — 代码中无敏感信息。
  6. 内存安全 — Qt 容器边界检查完善,QCache 自动管理内存,QSharedPointer 管理 renderer 生命周期。
  7. 测试中的 reinterpret_castut_pagerenderthread.cpp 中使用 reinterpret_cast<DocSheet *>(fake_sheet_storage) 是测试桩用法,所有成员调用已被 stub 替换,无实际内存安全风险,且有详细注释说明原因。

安全漏洞清单

无安全漏洞。


改进建议

建议1:提取共享反色逻辑(优先级:中)

BookMarkDelegateNotesDelegate 的深色主题反色代码高度重复,建议提取为共享工具函数(见维度2 修复代码示例),减少维护成本。

建议2:统一缓存配置(优先级:低)

BookMarkDelegatem_darkPixmapCache 建议显式设置 setMaxCost,与 NotesDelegate 保持一致:

// BookMarkDelegate 构造函数中添加
m_darkPixmapCache.setMaxCost(8 * 1024 * 1024); // 8MB 反色缓存预算

建议3:测试辅助类提取(优先级:低)

ThemeGuard 类在 4 个测试文件中重复定义,建议提取到共享测试头文件(如 tests/common/themeguard.h)。


审查结论

本 PR 修复了侧边栏缩略图在深色主题下仍显示白底的问题,改为统一跟随系统深浅主题并通过 NightFilter 主干管线反色。代码实现与 commit message 描述的目的完全一致:

  1. 功能正确 — 白底反色、图片对象蒙版保持原色、扫描页整页反色特判均已实现
  2. 架构统一 — 所有 sidebar delegate 统一走 NightFilter 管线,与主视图观感一致
  3. 性能优化 — Worker 线程预取 + 多级缓存,避免 UI 线程阻塞和重复计算
  4. 测试充分 — 7 个测试文件覆盖所有新增功能点和边界条件
  5. 安全无风险 — 纯渲染逻辑,无安全漏洞

主要改进点为 BookMarkDelegate 与 NotesDelegate 之间的代码重复,建议提取共享方法。整体代码质量优秀,建议合并。

Model.cpp is compiled with the global OFD_SUPPORT_ENABLED definition,
but batchprint-convert neither built OfdModel.cpp (which implements
OfdDocument::loadDocument) nor linked rofd_ffi/cairo, so linking
deepin-reader-batchprint failed with an undefined reference. Add
OfdModel.cpp and Navigation.cpp plus the rofd/cairo include dirs,
compile options and link libraries behind OFD_SUPPORT_ENABLED,
mirroring the existing XPS handling.

batchprint-convert 编译 Model.cpp 时继承了全局 OFD_SUPPORT_ENABLED 宏,
但既未编译实现 OfdDocument::loadDocument 的 OfdModel.cpp,也未链接
rofd_ffi/cairo,导致 deepin-reader-batchprint 链接报未定义引用。仿照
XPS 的处理方式,在 OFD_SUPPORT_ENABLED 下补充 OfdModel.cpp、
Navigation.cpp 及 rofd/cairo 头文件路径、编译选项与链接库。

Log: 修复批量打印 OFD 开启时缺少 OfdModel/rofd 链接导致的编译失败
Influence: 仅构建调整:OFD 支持开启时 batchprint 可正常编译链接,关闭 OFD 时行为不变。
@deepin-ci-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: add-uos, lzwind

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@add-uos

add-uos commented Sep 15, 2026

Copy link
Copy Markdown
Contributor Author

/merge

@deepin-bot
deepin-bot Bot merged commit 9fc57e1 into linuxdeepin:master Sep 15, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants