@@ -49,6 +49,9 @@ void ThumbModel::updatePix(int row, const QPixmap &pix) {
4949 m_items[row].pix = pix;
5050 QModelIndex idx = index (row);
5151 emit dataChanged (idx, idx, {PixRole});
52+ // IconMode + setUniformItemSizes 下 dataChanged 单行可能不触发重绘,强制刷新
53+ if (auto *v = qobject_cast<QListView*>(QObject::parent ()))
54+ v->viewport ()->update ();
5255}
5356
5457void ThumbModel::updateHighlight (int row, bool hit) {
@@ -145,24 +148,41 @@ static QImage loadImageNoIcc(const QString &path, int maxPx)
145148 if (!f.open (QIODevice::ReadOnly))
146149 return {};
147150 QByteArray data = f.readAll ();
148- // 非 PNG 或没有 iCCP → 直接走 QImageReader
151+ // 有 iCCP 被剥离 → 从内存深拷贝解码(QImage::fromData 会拷贝数据,无悬垂)
149152 if (data.startsWith (" \x89 PNG\r\n\x1a\n " )) {
150- QByteArray cleaned = data.left (8 );
153+ bool stripped = false ;
154+ QByteArray cleaned;
151155 qsizetype pos = 8 ;
152156 while (pos + 8 <= data.size ()) {
153157 const quint32 len = qFromBigEndian<quint32>(data.constData () + pos);
154- const QByteArray type = data.mid (pos + 4 , 4 );
155158 const qsizetype total = 12 + len;
156- if (pos + total > data.size ()) break ; // 损坏,交给 Qt 处理
157- if (type != " iCCP" )
158- cleaned += data.mid (pos, total);
159+ if (pos + total > data.size ()) break ;
160+ if (data.mid (pos + 4 , 4 ) == " iCCP" ) {
161+ stripped = true ;
162+ cleaned = data.left (8 );
163+ break ;
164+ }
159165 pos += total;
160166 }
161- if (cleaned.size () > 8 ) data = cleaned; // 有 chunk 才替换
167+ if (stripped) {
168+ pos = 8 ;
169+ while (pos + 8 <= data.size ()) {
170+ const quint32 len = qFromBigEndian<quint32>(data.constData () + pos);
171+ const QByteArray type = data.mid (pos + 4 , 4 );
172+ const qsizetype total = 12 + len;
173+ if (pos + total > data.size ()) break ;
174+ if (type != " iCCP" )
175+ cleaned += data.mid (pos, total);
176+ pos += total;
177+ }
178+ QImage img = QImage::fromData (cleaned);
179+ if (!img.isNull () && maxPx > 0 )
180+ img = img.scaled (maxPx, maxPx, Qt::KeepAspectRatio, Qt::SmoothTransformation);
181+ return img;
182+ }
162183 }
163- QBuffer buf (&data);
164- buf.open (QIODevice::ReadOnly);
165- QImageReader rd (&buf, " png" );
184+ // 无 iCCP:直接读文件路径(QImageReader 自管文件生命周期,无悬垂)
185+ QImageReader rd (path);
166186 rd.setAutoTransform (true );
167187 QSize orig = rd.size ();
168188 if (orig.isValid () && orig.width () > 0 && maxPx > 0 )
@@ -199,10 +219,15 @@ class FolderPreviewTask : public QRunnable {
199219 : m_grid(grid), m_folder(std::move(folder)), m_row(row), m_ts(ts), m_token(token) {}
200220
201221 void run () override {
202- QDir sub (m_folder);
203- sub.setNameFilters ({" *.jpg" ," *.jpeg" ," *.png" ," *.webp" ," *.bmp" ," *.gif" });
204- sub.setFilter (QDir::Files | QDir::Readable);
205- QStringList previews = sub.entryList ();
222+ // 只取前 4 个图片文件名(QDirIterator 前 4 个匹配即停,避免枚举整个目录)
223+ QStringList previews;
224+ QDirIterator it (m_folder,
225+ {" *.jpg" ," *.jpeg" ," *.png" ," *.webp" ," *.bmp" ," *.gif" },
226+ QDir::Files | QDir::Readable);
227+ while (it.hasNext () && previews.size () < 4 ) {
228+ it.next ();
229+ previews << it.fileName ();
230+ }
206231
207232 // 全程用 QImage(线程安全);QPixmap 只在主线程回调里创建
208233 QImage collage (m_ts, m_ts, QImage::Format_ARGB32_Premultiplied);
@@ -237,10 +262,12 @@ class FolderPreviewTask : public QRunnable {
237262 // 预览目标尺寸(cell 大小,最多 ~half ts)—— 用 setScaledSize 限流,
238263 // 避免解码 36MB 巨型 PNG 撑爆内存(之前崩溃根因)
239264 int targetPx = qMax (1 , (int )(qMax (cellW, cellH) * 1.5 ));
265+ int decoded = 0 ;
240266 for (int pi = 0 ; pi < sample; ++pi) {
241267 // 剥离损坏 ICC 后解码(同 ThumbDecodeTask)
242- QImage img = loadImageNoIcc (sub. path () + " /" + previews[pi], targetPx);
268+ QImage img = loadImageNoIcc (m_folder + " /" + previews[pi], targetPx);
243269 if (img.isNull ()) continue ;
270+ ++decoded;
244271 int col = pi % cellCnt, row = pi / cellCnt;
245272 QRectF cell (inner.x () + col*cellW, inner.y () + row*cellH, cellW, cellH);
246273 QSize fit = img.size ();
@@ -251,9 +278,9 @@ class FolderPreviewTask : public QRunnable {
251278 }
252279 p.end ();
253280
254- QMetaObject::invokeMethod (m_grid, [g = m_grid, r = m_row, im = collage, t = m_token]() {
255- if (g) g-> applyFolderThumb (r, im, t);
256- }, Qt::QueuedConnection );
281+ // 主线程同步调用(本任务由主线程直接 run(),无需投递事件队列——避免连续切换时事件堆积)
282+ if (m_grid)
283+ m_grid-> applyFolderThumb (m_row, collage, m_token );
257284 }
258285
259286private:
@@ -352,10 +379,10 @@ void ThumbnailGrid::loadFolder(const QString &path) {
352379 int dirCount = (int )m_subDirs.size ();
353380 int token = m_loadToken; // 本次加载令牌
354381
355- // 文件夹预览任务
356- // 文件夹预览任务
357- for ( int di = 0 ; di < dirCount; ++di)
358- pool-> start ( new FolderPreviewTask ( this , m_allItems[di]. path , di, ts, token) );
382+ // 文件夹预览任务(主线程分批处理,避免大目录并发抢线程池导致部分任务滞留)
383+ m_folderPreviewIdx = 0 ;
384+ m_folderPreviewToken = token;
385+ processNextFolderPreview ( ts, token);
359386
360387 // 图片:缓存命中直接显示,未命中提交解码
361388 for (int i = 0 ; i < (int )m_imageFiles.size (); ++i) {
@@ -372,6 +399,29 @@ void ThumbnailGrid::loadFolder(const QString &path) {
372399 }
373400}
374401
402+ // 主线程分批处理文件夹拼图(每次 2 个,避免 46 个大目录并发卡死)
403+ void ThumbnailGrid::processNextFolderPreview (int ts, int token) {
404+ // 每次进入都检查 token:用户已切换目录则丢弃(loadFolder 清空了 m_allItems)
405+ if (token != m_loadToken || m_folderPreviewIdx >= (int )m_subDirs.size ())
406+ return ;
407+ const int batch = 2 ;
408+ for (int n = 0 ; n < batch && m_folderPreviewIdx < (int )m_subDirs.size (); ++n, ++m_folderPreviewIdx) {
409+ // 每次迭代检查 token:task->run() 的 updatePix→dataChanged 可能重入 loadFolder
410+ // 清空 m_allItems,再访问就崩溃(SEH 0xc0000005 已证实)
411+ if (token != m_loadToken)
412+ return ;
413+ int di = m_folderPreviewIdx;
414+ // 用路径快照(m_allItems 可能被新 loadFolder 重建,不能跨批持有索引)
415+ QString dirPath = m_allItems[di].path ;
416+ FolderPreviewTask *task = new FolderPreviewTask (this , dirPath, di, ts, token);
417+ task->run ();
418+ delete task;
419+ }
420+ if (m_folderPreviewIdx < (int )m_subDirs.size () && token == m_loadToken) {
421+ QTimer::singleShot (0 , this , [this , ts, token]{ processNextFolderPreview (ts, token); });
422+ }
423+ }
424+
375425// 按路径在模型可见行中查找
376426int ThumbnailGrid::findModelRow (const QString &path) const {
377427 for (int i = 0 ; i < m_model->rowCount (); ++i) {
0 commit comments