diff --git a/CMakeLists.txt b/CMakeLists.txt index ab46e05..b013fa3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,11 +35,12 @@ add_executable(cutefish-filemanager desktop/desktopsettings.cpp helper/datehelper.cpp - helper/thumbnailer.cpp helper/pathhistory.cpp helper/fm.cpp helper/shortcut.cpp - helper/thumbnailerjob.cpp + + thumbnailer/thumbnailprovider.cpp + thumbnailer/thumbnailcache.cpp desktopiconprovider.cpp diff --git a/desktop/desktopview.cpp b/desktop/desktopview.cpp index 58f6d14..2410768 100644 --- a/desktop/desktopview.cpp +++ b/desktop/desktopview.cpp @@ -18,7 +18,7 @@ */ #include "desktopview.h" -#include "helper/thumbnailer.h" +#include "thumbnailer/thumbnailprovider.h" #include #include @@ -39,7 +39,7 @@ DesktopView::DesktopView(QQuickView *parent) KWindowSystem::setState(winId(), NET::KeepBelow); engine()->rootContext()->setContextProperty("desktopView", this); - engine()->addImageProvider("thumbnailer", new Thumbnailer()); + engine()->addImageProvider("thumbnailer", new ThumbnailProvider()); setTitle(tr("Desktop")); setScreen(qApp->primaryScreen()); diff --git a/helper/thumbnailer.cpp b/helper/thumbnailer.cpp deleted file mode 100644 index ba7c2b2..0000000 --- a/helper/thumbnailer.cpp +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2021 CutefishOS Team. - * - * Author: revenmartin - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "thumbnailer.h" - -#include - -#include "thumbnailerjob.h" - -#include -#include - -QQuickImageResponse *Thumbnailer::requestImageResponse(const QString &id, const QSize &requestedSize) -{ - AsyncImageResponse *response = new AsyncImageResponse(id, requestedSize); - return response; -} - -AsyncImageResponse::AsyncImageResponse(const QString &id, const QSize &requestedSize) - : m_id(id) - , m_requestedSize(requestedSize) -{ - auto job_ = new ThumbnailerJob(QUrl::fromUserInput(id).toLocalFile(), requestedSize); - connect(job_, &ThumbnailerJob::gotPreview, [this] (QPixmap pixmap) { - m_image = pixmap.toImage(); - emit this->finished(); - }); - - connect(job_, &ThumbnailerJob::failed, [this] { - emit this->cancel(); - emit this->finished(); - }); - - job_->start(); - - - -// QStringList plugins = KIO::PreviewJob::defaultPlugins(); -// auto job = new KIO::PreviewJob(KFileItemList() << KFileItem(QUrl::fromUserInput(id)), requestedSize, &plugins); - -// connect(job, &KIO::PreviewJob::gotPreview, [this](KFileItem, QPixmap pixmap) { -// m_image = pixmap.toImage(); -// emit this->finished(); -// }); - -// connect(job, &KIO::PreviewJob::failed, [this](KFileItem) { -// emit this->cancel(); -// emit this->finished(); -// }); - -// job->start(); -} - -QQuickTextureFactory *AsyncImageResponse::textureFactory() const -{ - return QQuickTextureFactory::textureFactoryForImage(m_image); -} diff --git a/helper/thumbnailerjob.cpp b/helper/thumbnailerjob.cpp deleted file mode 100644 index 67cf305..0000000 --- a/helper/thumbnailerjob.cpp +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (C) 2021 CutefishOS Team. - * - * Author: Reion Wong - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "thumbnailerjob.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -ThumbnailerJob::ThumbnailerJob(const QString &fileName, const QSize &size, QObject *parent) - : QThread(parent) - , m_url(QUrl::fromUserInput(fileName)) - , m_size(size) - , shmaddr(nullptr) - , shmid(-1) -{ - // http://specifications.freedesktop.org/thumbnail-spec/thumbnail-spec-latest.html#DIRECTORY - m_thumbnailsDir = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1String("/thumbnails/"); -} - -ThumbnailerJob::~ThumbnailerJob() -{ - if (shmaddr) { - shmdt((char *)shmaddr); - shmctl(shmid, IPC_RMID, nullptr); - } -} - -void ThumbnailerJob::run() -{ - if (!QFile::exists(m_url.toLocalFile())) { - emit failed(); - return; - } - - int width = m_size.width(); - int height = m_size.height(); - int cacheSize = 0; - bool needCache = false; - - // 首先需要找到目录 - if (width <= 128 && height <= 128) - cacheSize = 128; - else if (width <= 256 && height <= 256) - cacheSize = 256; - else - cacheSize = 512; - - struct CachePool { - QString path; - int minSize; - }; - - const static auto pools = { - CachePool{QStringLiteral("/normal/"), 128}, - CachePool{QStringLiteral("/large/"), 256}, - CachePool{QStringLiteral("/x-large/"), 512}, - CachePool{QStringLiteral("/xx-large/"), 1024}, - }; - - QString thumbDir; - int wants = /*devicePixelRatio **/ cacheSize; - for (const auto &p : pools) { - if (p.minSize < wants) { - continue; - } else { - thumbDir = p.path; - break; - } - } - - m_thumbnailsPath = m_thumbnailsDir + thumbDir; - - // 不存在需要创建路径 - if (!QDir(m_thumbnailsPath).exists()) { - if (QDir().mkpath(m_thumbnailsPath)) { - QFile f(m_thumbnailsPath); - f.setPermissions(QFile::ReadUser | QFile::WriteUser | QFile::ExeUser); // 0700 - } - } - - // 求出文件的 md5 - QByteArray origName; - const QFileInfo info(m_url.toLocalFile()); - const QString canonicalPath = info.canonicalFilePath(); - origName = QUrl::fromLocalFile(canonicalPath).toEncoded(QUrl::RemovePassword | QUrl::FullyEncoded); - - if (origName.isEmpty()) { - emit failed(); - return; - } - - QCryptographicHash md5(QCryptographicHash::Md5); - md5.addData(origName); - // Image size - md5.addData(QString::number(m_size.width()).toStdString().c_str()); - md5.addData(QString::number(m_size.height()).toStdString().c_str()); - // Time - md5.addData(QString::number(info.lastModified().toTime_t()).toStdString().c_str()); - - m_thumbnailsName = QString::fromLatin1(md5.result().toHex()) + QLatin1String(".png"); - - // 是否需要生成缓存 - needCache = !QFile::exists(m_thumbnailsPath + m_thumbnailsName); - - if (needCache) { - QFile f(m_url.toLocalFile()); - if (f.open(QIODevice::ReadOnly)) { - QByteArray data = f.readAll(); - QImage thumb; - thumb.loadFromData(data); - thumb = thumb.scaled(m_size, Qt::KeepAspectRatio, Qt::SmoothTransformation); - - // 保存 cache 文件 - QSaveFile saveFile(m_thumbnailsPath + m_thumbnailsName); - if (saveFile.open(QIODevice::WriteOnly)) { - if (thumb.save(&saveFile, "PNG")) { - saveFile.commit(); - } - } - - emitPreview(thumb); - } - } else { - QFile f(m_thumbnailsPath + m_thumbnailsName); - if (f.open(QIODevice::ReadOnly)) { - QByteArray data = f.readAll(); - QImage thumb; - thumb.loadFromData(data); - emitPreview(thumb); - } - } -} - -void ThumbnailerJob::emitPreview(const QImage &image) -{ - QPixmap pixmap; - - if (image.width() > m_size.width() || image.height() > m_size.height()) { - pixmap = QPixmap::fromImage(image.scaled(m_size, Qt::KeepAspectRatio, Qt::SmoothTransformation)); - } else { - pixmap = QPixmap::fromImage(image); - } - - emit gotPreview(pixmap); -} diff --git a/main.cpp b/main.cpp index c4ede89..07e8da9 100644 --- a/main.cpp +++ b/main.cpp @@ -33,11 +33,12 @@ #include "widgets/itemviewadapter.h" #include "desktop/desktopsettings.h" #include "desktop/desktopview.h" -#include "helper/thumbnailer.h" #include "helper/datehelper.h" #include "helper/fm.h" #include "helper/shortcut.h" +#include "thumbnailer/thumbnailprovider.h" + int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); @@ -68,7 +69,7 @@ int main(int argc, char *argv[]) qmlRegisterType(uri, 1, 0, "DesktopSettings"); qmlRegisterType(uri, 1, 0, "Fm"); qmlRegisterType(uri, 1, 0, "ShortCut"); - // qmlRegisterAnonymousType(uri, 1); + qmlRegisterAnonymousType(uri, 1); QCommandLineParser parser; parser.setApplicationDescription(QStringLiteral("File Manager")); @@ -121,7 +122,7 @@ int main(int argc, char *argv[]) } engine.load(url); - engine.addImageProvider("thumbnailer", new Thumbnailer()); + engine.addImageProvider("thumbnailer", new ThumbnailProvider()); return app.exec(); } diff --git a/thumbnailer/thumbnailcache.cpp b/thumbnailer/thumbnailcache.cpp new file mode 100644 index 0000000..d148879 --- /dev/null +++ b/thumbnailer/thumbnailcache.cpp @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2021 CutefishOS Team. + * + * Author: Reion Wong + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "thumbnailcache.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static ThumbnailCache *SELF = nullptr; + +QImage scaleImage(const QImage &image, const QSize &requestedSize, bool crop, Qt::TransformationMode mode) +{ + const QImage scaledImage(image.size() != requestedSize + ? image.scaled(requestedSize, crop ? Qt::KeepAspectRatioByExpanding : Qt::KeepAspectRatio, mode) + : image); + + if (crop && scaledImage.size() != requestedSize) { + QRect cropRect(0, 0, requestedSize.width(), requestedSize.height()); + cropRect.moveCenter(QPoint(scaledImage.width() / 2, scaledImage.height() / 2)); + + return scaledImage.copy(cropRect); + } else { + return scaledImage; + } +} + +ThumbnailCache *ThumbnailCache::self() +{ + if (!SELF) { + SELF = new ThumbnailCache; + } + + return SELF; +} + +ThumbnailCache::ThumbnailCache(QObject *parent) + : QObject(parent) +{ + m_thumbnailsDir = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1String("/thumbnails/"); + + QDir dir(m_thumbnailsDir); + if (!dir.exists()) + dir.mkdir(m_thumbnailsDir); +} + +QString ThumbnailCache::requestThumbnail(const QString &filePath, const QSize &requestedSize) +{ + QString path(filePath); + if (path.startsWith("file://")) { + path = path.mid(7); + } + + if (!QFile::exists(path)) { + return QString(); + } + + int width = requestedSize.width(); + int height = requestedSize.height(); + int cacheSize = 0; + + // 首先需要找到目录 + if (width <= 128 && height <= 128) + cacheSize = 128; + else if (width <= 256 && height <= 256) + cacheSize = 256; + else + cacheSize = 512; + + struct CachePool { + QString path; + int minSize; + }; + + const static auto pools = { + CachePool{ QStringLiteral("/normal/"), 128 }, + CachePool{ QStringLiteral("/large/"), 256 }, + CachePool{ QStringLiteral("/x-large/"), 512 }, + CachePool{ QStringLiteral("/xx-large/"), 1024 }, + }; + + QString thumbDir; + int wants = /*devicePixelRatio **/ cacheSize; + for (const auto &p : pools) { + if (p.minSize < wants) { + continue; + } else { + thumbDir = p.path; + break; + } + } + + // 尺寸文件夹 + QString sizeDir = m_thumbnailsDir + thumbDir; + + // 不存在需要创建路径 + if (!QDir(sizeDir).exists()) { + if (QDir().mkpath(sizeDir)) { + QFile f(sizeDir); + f.setPermissions(QFile::ReadUser | QFile::WriteUser | QFile::ExeUser); // 0700 + } + } + + // Md5 + QByteArray origName; + const QFileInfo info(path); + const QString canonicalPath = info.canonicalFilePath(); + origName = QUrl::fromLocalFile(canonicalPath).toEncoded(QUrl::RemovePassword | QUrl::FullyEncoded); + + if (origName.isEmpty()) { + return QString(); + } + + QCryptographicHash md5(QCryptographicHash::Md5); + md5.addData(origName); + // Image size + md5.addData(QString::number(width).toStdString().c_str()); + md5.addData(QString::number(height).toStdString().c_str()); + // Time + md5.addData(QString::number(info.lastModified().toTime_t()).toStdString().c_str()); + + QString thumbnailsName = QString::fromLatin1(md5.result().toHex()) + QLatin1String(".png"); + QString thumbnailsPath = m_thumbnailsDir + thumbnailsName; + + if (!QFile::exists(thumbnailsPath)) { + return generateThumbnail(path, thumbnailsPath, requestedSize); + } else { + return thumbnailsPath; + } +} + +QString ThumbnailCache::generateThumbnail(const QString &source, const QString &target, const QSize &requestedSize) +{ + QImageReader reader(source); + if (reader.canRead()) { + // Quality in the jpeg reader is binary. >= 50: high quality, < 50 fast + reader.setQuality(49); + + reader.setScaledSize(reader.size().scaled(requestedSize.width(), + requestedSize.height(), Qt::KeepAspectRatio)); + reader.setAutoTransform(true); + + QImage image(reader.read()); + return writeCacheFile(target, image); + } + + return QString(); +} + +QString ThumbnailCache::writeCacheFile(const QString &path, const QImage &image) +{ + const QString thumbnailPath(path); + QFile thumbnailFile(path); + + if (!thumbnailFile.open(QIODevice::WriteOnly)) { + return QString(); + } + + image.save(&thumbnailFile, image.hasAlphaChannel() ? "PNG" : "JPG"); + thumbnailFile.flush(); + thumbnailFile.close(); + + return thumbnailPath; +} diff --git a/helper/thumbnailerjob.h b/thumbnailer/thumbnailcache.h similarity index 58% rename from helper/thumbnailerjob.h rename to thumbnailer/thumbnailcache.h index 10ead87..8edecfd 100644 --- a/helper/thumbnailerjob.h +++ b/thumbnailer/thumbnailcache.h @@ -17,43 +17,27 @@ * along with this program. If not, see . */ -#ifndef THUMBNAILERJOB_H -#define THUMBNAILERJOB_H +#ifndef THUMBNAILCACHE_H +#define THUMBNAILCACHE_H #include -#include -#include -#include -class ThumbnailerJob : public QThread +class QImageReader; +class ThumbnailCache : public QObject { Q_OBJECT public: - explicit ThumbnailerJob(const QString &fileName, const QSize &size, QObject *parent = nullptr); - ~ThumbnailerJob(); + static ThumbnailCache *self(); + explicit ThumbnailCache(QObject *parent = nullptr); - void run() override; - -signals: - void gotPreview(const QPixmap &pixmap); - void failed(); + QString requestThumbnail(const QString &filePath, const QSize &requestedSize); + QString generateThumbnail(const QString &source, const QString &target, const QSize &requestedSize); + QString writeCacheFile(const QString &path, const QImage &image); private: - void emitPreview(const QImage &image); - -private: - QUrl m_url; - QSize m_size; - // thumbnail cache folder QString m_thumbnailsDir; - QString m_thumbnailsPath; - QString m_thumbnailsName; - - uchar *shmaddr; - size_t shmsize; - int shmid; }; -#endif // THUMBNAILERJOB_H +#endif // THUMBNAILCACHE_H diff --git a/thumbnailer/thumbnailprovider.cpp b/thumbnailer/thumbnailprovider.cpp new file mode 100644 index 0000000..f77399d --- /dev/null +++ b/thumbnailer/thumbnailprovider.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2021 CutefishOS Team. + * + * Author: Reion Wong + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "thumbnailprovider.h" +#include "thumbnailcache.h" + +#include +#include +#include + +QImage ThumbnailProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize) +{ + if (!requestedSize.isValid()) { + return QImage(); + } + + if (size) + *size = requestedSize; + + QString thumbnail = ThumbnailCache::self()->requestThumbnail(id, requestedSize); + + if (!thumbnail.isEmpty()) { + return QImage(thumbnail); + } + + return QImage(); +} diff --git a/helper/thumbnailer.h b/thumbnailer/thumbnailprovider.h similarity index 56% rename from helper/thumbnailer.h rename to thumbnailer/thumbnailprovider.h index cb9b100..48e25ee 100644 --- a/helper/thumbnailer.h +++ b/thumbnailer/thumbnailprovider.h @@ -1,7 +1,7 @@ /* * Copyright (C) 2021 CutefishOS Team. * - * Author: revenmartin + * Author: Reion Wong * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -17,28 +17,20 @@ * along with this program. If not, see . */ -#ifndef THUMBNAILER_H -#define THUMBNAILER_H +#ifndef THUMBNAILPROVIDER_H +#define THUMBNAILPROVIDER_H -#include #include -class AsyncImageResponse : public QQuickImageResponse +class ThumbnailProvider : public QQuickImageProvider { public: - AsyncImageResponse(const QString &id, const QSize &requestedSize); - QQuickTextureFactory *textureFactory() const override; + ThumbnailProvider() + : QQuickImageProvider(QQuickImageProvider::Image) + { + } -private: - QString m_id; - QSize m_requestedSize; - QImage m_image; + QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize); }; -class Thumbnailer : public QQuickAsyncImageProvider -{ -public: - QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override; -}; - -#endif // THUMBNAILER_H +#endif // THUMBNAILPROVIDER_H