diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f3a76d4..788361f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,518 +1,519 @@ cmake_minimum_required(VERSION 3.2.0) project(kphotoalbum VERSION 5.4) if(POLICY CMP0063) cmake_policy(SET CMP0063 NEW) endif() # provide drop-down menu for build-type in cmake-gui: set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS ";Debug;Release;RelWithDebInfo;MinSizeRel") list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) find_package(ECM REQUIRED NO_MODULE) list(APPEND CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ) include(KDEInstallDirs) include(KDECompilerSettings) include(KDECMakeSettings) include(FeatureSummary) # enable exceptions: kde_enable_exceptions() add_definitions( -DQT_NO_CAST_FROM_ASCII -DQT_NO_CAST_TO_ASCII -DQT_NO_URL_CAST_FROM_STRING -DQT_NO_CAST_FROM_BYTEARRAY -DQT_DEPRECATED_WARNINGS ) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_REQUIRED TRUE) ########### dependencies ############### find_package(Qt5 REQUIRED COMPONENTS Sql Xml Widgets Network) find_package(Phonon4Qt5 REQUIRED) find_package(KF5 REQUIRED COMPONENTS Archive Completion Config CoreAddons DocTools I18n IconThemes JobWidgets KIO TextWidgets XmlGui WidgetsAddons) find_package(JPEG REQUIRED) if(JPEG_FOUND) include_directories(${JPEG_INCLUDE_DIR}) endif() find_package(Exiv2 REQUIRED) find_package(KF5Kipi 5.1.0) set_package_properties(KF5Kipi PROPERTIES TYPE RECOMMENDED PURPOSE "Enables integration of KDE image plugin interface (shared functionality between KPhotoAlbum and other apps like gwenview or digikam)" ) set(HASKIPI ${KF5Kipi_FOUND}) find_package(KF5KDcraw) set_package_properties(KF5KDcraw PROPERTIES TYPE OPTIONAL PURPOSE "Enables RAW image support" ) set(HAVE_KDCRAW ${KF5KDcraw_FOUND} ) find_package(KF5KGeoMap) set_package_properties(KF5KGeoMap PROPERTIES TYPE OPTIONAL PURPOSE "Enables support for geographic map location using embedded GPS information." ) set(HAVE_KGEOMAP ${KF5KGeoMap_FOUND}) add_custom_target( UpdateVersion ALL COMMAND ${CMAKE_COMMAND} -DBASE_DIR=${CMAKE_CURRENT_SOURCE_DIR} -DPROJECT_NAME=KPA -DPROJECT_VERSION="${PROJECT_VERSION}" -DCMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE}" -P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/UpdateVersion.cmake" COMMENT "Updating version header." BYPRODUCTS "${CMAKE_CURRENT_SOURCE_DIR}/version.h" ) # For config-kpa-*.h include_directories(${CMAKE_CURRENT_BINARY_DIR}) set(libdatebar_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/DateBar/documentation.h ${CMAKE_CURRENT_SOURCE_DIR}/DateBar/DateBarWidget.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DateBar/ViewHandler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DateBar/MouseHandler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DateBar/MouseHandler.cpp ) set(libSettings_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/Settings/documentation.h ${CMAKE_CURRENT_SOURCE_DIR}/Settings/SettingsData.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Settings/SettingsDialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Settings/ViewerSizeConfig.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Settings/CategoryItem.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Settings/CategoryPage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Settings/TagGroupsPage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Settings/GeneralPage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Settings/FileVersionDetectionPage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Settings/ThumbnailsPage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Settings/ViewerPage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Settings/DatabaseBackendPage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Settings/UntaggedGroupBox.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Settings/CategoriesGroupsWidget.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Settings/BirthdayPage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Settings/DateTableWidgetItem.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Settings/Logging.cpp ) set(libxmldb_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/XMLDB/documentation.h ${CMAKE_CURRENT_SOURCE_DIR}/XMLDB/Database.cpp ${CMAKE_CURRENT_SOURCE_DIR}/XMLDB/XMLCategoryCollection.cpp ${CMAKE_CURRENT_SOURCE_DIR}/XMLDB/XMLCategory.cpp ${CMAKE_CURRENT_SOURCE_DIR}/XMLDB/XMLImageDateCollection.cpp ${CMAKE_CURRENT_SOURCE_DIR}/XMLDB/NumberedBackup.cpp ${CMAKE_CURRENT_SOURCE_DIR}/XMLDB/FileReader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/XMLDB/FileWriter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/XMLDB/ElementWriter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/XMLDB/XmlReader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/XMLDB/CompressFileInfo.cpp ${CMAKE_CURRENT_SOURCE_DIR}/XMLDB/Logging.cpp ) set(libThumbnailView_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/documentation.h ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/ThumbnailRequest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/ThumbnailToolTip.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/ThumbnailWidget.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/GridResizeInteraction.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/GridResizeSlider.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/SelectionInteraction.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/MouseTrackingInteraction.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/CellGeometry.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/ThumbnailModel.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/ThumbnailFacade.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/ThumbnailComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/KeyboardEventHandler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/ThumbnailDND.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/Delegate.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/SelectionMaintainer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/VideoThumbnailCycler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/Logging.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/MouseInteraction.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/ThumbnailFactory.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ThumbnailView/enums.cpp ) set(libPlugins_SRCS) if(KF5Kipi_FOUND) set(libPlugins_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/Plugins/documentation.h ${CMAKE_CURRENT_SOURCE_DIR}/Plugins/Interface.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Plugins/ImageCollection.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Plugins/ImageInfo.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Plugins/CategoryImageCollection.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Plugins/ImageCollectionSelector.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Settings/PluginsPage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Plugins/UploadWidget.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Plugins/UploadImageCollection.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Plugins/Logging.cpp ) endif() set(libViewer_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/Viewer/documentation.h ${CMAKE_CURRENT_SOURCE_DIR}/Viewer/ViewerWidget.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Viewer/ImageDisplay.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Viewer/ViewHandler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Viewer/SpeedDisplay.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Viewer/InfoBox.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Viewer/CategoryImageConfig.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Viewer/AbstractDisplay.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Viewer/VideoDisplay.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Viewer/TextDisplay.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Viewer/InfoBoxResizer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Viewer/VisibleOptionsMenu.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Viewer/VideoShooter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Viewer/TaggedArea.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Viewer/Logging.cpp ) set(libCategoryListView_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/CategoryListView/documentation.h ${CMAKE_CURRENT_SOURCE_DIR}/CategoryListView/DragableTreeWidget.cpp ${CMAKE_CURRENT_SOURCE_DIR}/CategoryListView/CheckDropItem.cpp ${CMAKE_CURRENT_SOURCE_DIR}/CategoryListView/DragItemInfo.cpp ${CMAKE_CURRENT_SOURCE_DIR}/CategoryListView/Logging.cpp ) set(libHTMLGenerator_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/HTMLGenerator/documentation.h ${CMAKE_CURRENT_SOURCE_DIR}/HTMLGenerator/HTMLDialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/HTMLGenerator/Generator.cpp ${CMAKE_CURRENT_SOURCE_DIR}/HTMLGenerator/Setup.cpp ${CMAKE_CURRENT_SOURCE_DIR}/HTMLGenerator/ImageSizeCheckBox.h ${CMAKE_CURRENT_SOURCE_DIR}/HTMLGenerator/Logging.cpp ${CMAKE_CURRENT_SOURCE_DIR}/HTMLGenerator/ImageSizeCheckBox.cpp ) set(libUtilities_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/documentation.h ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/AlgorithmHelper.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/ShowBusyCursor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/List.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/UniqFilenameMapper.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/Util.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/BooleanGuard.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/Process.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/DeleteFiles.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/ToolTip.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/Logging.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/JpeglibWithFix.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/QStr.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/StringSet.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Utilities/FastJpeg.cpp ) set(libMainWindow_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/documentation.h ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/DeleteDialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/RunDialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/FeatureDialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/InvalidDateFinder.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/AutoStackImages.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/TokenEditor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/WelcomeDialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/Window.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/SplashScreen.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/ExternalPopup.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/CategoryImagePopup.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/SearchBar.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/ImageCounter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/DirtyIndicator.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/StatisticsDialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/BreadcrumbViewer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/StatusBar.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/UpdateVideoThumbnail.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/DuplicateMerger/DuplicateMerger.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/DuplicateMerger/DuplicateMatch.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/DuplicateMerger/MergeToolTip.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/CopyPopup.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/Options.cpp ${CMAKE_CURRENT_SOURCE_DIR}/MainWindow/Logging.cpp ) set(libImageManager_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/documentation.h ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/ImageLoaderThread.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/AsyncLoader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/ImageRequest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/ImageClientInterface.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/ImageDecoder.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/RawImageDecoder.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/RequestQueue.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/ThumbnailCache.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/ImageEvent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/ThumbnailBuilder.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/PreloadRequest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/CancelEvent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/VideoImageRescaleRequest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/VideoThumbnails.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/VideoLengthExtractor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/ExtractOneVideoFrame.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/Logging.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/CacheFileInfo.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImageManager/enums.cpp ) set(libDB_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/DB/documentation.h ${CMAKE_CURRENT_SOURCE_DIR}/DB/ImageInfo.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/Category.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/CategoryCollection.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/ExactCategoryMatcher.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/ImageDate.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/MD5Map.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/MemberMap.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/ImageInfoList.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/ImageDB.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/FileInfo.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/NegationCategoryMatcher.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/NewImageFinder.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/ImageScout.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/NoTagCategoryMatcher.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/GroupCounter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/CategoryMatcher.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/ImageSearchInfo.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/CategoryItem.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/ContainerCategoryMatcher.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/ValueCategoryMatcher.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/OrCategoryMatcher.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/AndCategoryMatcher.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/FastDir.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/OptimizedFileList.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/FileName.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/FileNameList.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/Logging.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/CategoryPtr.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/ExifMode.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/ImageDateCollection.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/ImageInfoPtr.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/MD5.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/MediaCount.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/RawId.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DB/SimpleCategoryMatcher.cpp ) set(libImportExport_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/ImportExport/documentation.h ${CMAKE_CURRENT_SOURCE_DIR}/ImportExport/Export.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImportExport/Import.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImportExport/ImportMatcher.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImportExport/XMLHandler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImportExport/MiniViewer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImportExport/ImportHandler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImportExport/ImageRow.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImportExport/ImportDialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImportExport/ImportSettings.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImportExport/KimFileReader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImportExport/MD5CheckPage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ImportExport/Logging.cpp ) set(libAnnotationDialog_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/AnnotationDialog/documentation.h ${CMAKE_CURRENT_SOURCE_DIR}/AnnotationDialog/Dialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/AnnotationDialog/ListSelect.cpp ${CMAKE_CURRENT_SOURCE_DIR}/AnnotationDialog/ImagePreview.cpp ${CMAKE_CURRENT_SOURCE_DIR}/AnnotationDialog/ImagePreviewWidget.cpp ${CMAKE_CURRENT_SOURCE_DIR}/AnnotationDialog/DateEdit.cpp ${CMAKE_CURRENT_SOURCE_DIR}/AnnotationDialog/CompletableLineEdit.cpp ${CMAKE_CURRENT_SOURCE_DIR}/AnnotationDialog/ListViewItemHider.cpp ${CMAKE_CURRENT_SOURCE_DIR}/AnnotationDialog/ShowSelectionOnlyManager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/AnnotationDialog/ShortCutManager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/AnnotationDialog/ResizableFrame.cpp ${CMAKE_CURRENT_SOURCE_DIR}/AnnotationDialog/DescriptionEdit.cpp ${CMAKE_CURRENT_SOURCE_DIR}/AnnotationDialog/AreaTagSelectDialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/AnnotationDialog/Logging.cpp ${CMAKE_CURRENT_SOURCE_DIR}/AnnotationDialog/enums.cpp ) set(libBrowser_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/Browser/documentation.h ${CMAKE_CURRENT_SOURCE_DIR}/Browser/BrowserWidget.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Browser/BrowserPage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Browser/OverviewPage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Browser/CategoryPage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Browser/ImageViewPage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Browser/TreeFilter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Browser/Breadcrumb.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Browser/BreadcrumbList.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Browser/AbstractCategoryModel.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Browser/FlatCategoryModel.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Browser/TreeCategoryModel.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Browser/CenteringIconView.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Browser/Logging.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Browser/enums.cpp ) set(libExif_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/Exif/documentation.h ${CMAKE_CURRENT_SOURCE_DIR}/Exif/Database.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Exif/InfoDialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Exif/SearchDialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Exif/SearchInfo.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Exif/TreeView.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Exif/Info.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Exif/RangeWidget.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Exif/DatabaseElement.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Exif/ReReadDialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Exif/Grid.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Exif/Logging.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Exif/SearchDialogSettings.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Settings/ExifPage.cpp ) set(libBackgroundTaskManager_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/BackgroundTaskManager/JobInterface.cpp ${CMAKE_CURRENT_SOURCE_DIR}/BackgroundTaskManager/JobManager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/BackgroundTaskManager/StatusIndicator.cpp ${CMAKE_CURRENT_SOURCE_DIR}/BackgroundTaskManager/JobViewer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/BackgroundTaskManager/JobModel.cpp ${CMAKE_CURRENT_SOURCE_DIR}/BackgroundTaskManager/JobInfo.cpp ${CMAKE_CURRENT_SOURCE_DIR}/BackgroundTaskManager/CompletedJobInfo.cpp ${CMAKE_CURRENT_SOURCE_DIR}/BackgroundTaskManager/Priority.cpp ${CMAKE_CURRENT_SOURCE_DIR}/BackgroundTaskManager/PriorityQueue.cpp ${CMAKE_CURRENT_SOURCE_DIR}/BackgroundTaskManager/Logging.cpp ) set(libBackgroundJobs_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/BackgroundJobs/SearchForVideosWithoutLengthInfo.cpp ${CMAKE_CURRENT_SOURCE_DIR}/BackgroundJobs/ReadVideoLengthJob.cpp ${CMAKE_CURRENT_SOURCE_DIR}/BackgroundJobs/SearchForVideosWithoutVideoThumbnailsJob.cpp ${CMAKE_CURRENT_SOURCE_DIR}/BackgroundJobs/HandleVideoThumbnailRequestJob.cpp ${CMAKE_CURRENT_SOURCE_DIR}/BackgroundJobs/ExtractOneThumbnailJob.cpp ) set(libRemoteControl_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/RemoteControl/RemoteCommand.cpp ${CMAKE_CURRENT_SOURCE_DIR}/RemoteControl/RemoteConnection.cpp ${CMAKE_CURRENT_SOURCE_DIR}/RemoteControl/Server.cpp ${CMAKE_CURRENT_SOURCE_DIR}/RemoteControl/RemoteInterface.cpp ${CMAKE_CURRENT_SOURCE_DIR}/RemoteControl/SearchInfo.cpp ${CMAKE_CURRENT_SOURCE_DIR}/RemoteControl/RemoteImageRequest.cpp ${CMAKE_CURRENT_SOURCE_DIR}/RemoteControl/ImageNameStore.cpp ${CMAKE_CURRENT_SOURCE_DIR}/RemoteControl/ConnectionIndicator.cpp ${CMAKE_CURRENT_SOURCE_DIR}/RemoteControl/Logging.cpp ) set(libMap_SRCS) if(KF5KGeoMap_FOUND) set(libMap_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/Browser/GeoPositionPage.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Browser/PositionBrowserWidget.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Map/MapView.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Map/MapMarkerModelHelper.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Map/SearchMarkerTiler.cpp ${CMAKE_CURRENT_SOURCE_DIR}/Map/Logging.cpp ) endif() add_subdirectory(images) add_subdirectory(icons) add_subdirectory(demo) add_subdirectory(themes) add_subdirectory(scripts) add_subdirectory(doc) ########### next target ############### set(kphotoalbum_SRCS main.cpp ${libdatebar_SRCS} ${libSettings_SRCS} ${libsurvey_SRCS} ${libxmldb_SRCS} ${libThumbnailView_SRCS} ${libPlugins_SRCS} ${libViewer_SRCS} ${libCategoryListView_SRCS} ${libHTMLGenerator_SRCS} ${libMainWindow_SRCS} ${libImageManager_SRCS} ${libDB_SRCS} ${libImportExport_SRCS} ${libAnnotationDialog_SRCS} ${libExif_SRCS} ${libBrowser_SRCS} ${libBackgroundTaskManager_SRCS} ${libBackgroundJobs_SRCS} ${libRemoteControl_SRCS} ${libMap_SRCS} ${libUtilities_SRCS} # add doxygen headers so that they get visibiltiy in IDEs: ${CMAKE_CURRENT_SOURCE_DIR}/documentation/coding-standards.h ${CMAKE_CURRENT_SOURCE_DIR}/documentation/mainpage.h ${CMAKE_CURRENT_SOURCE_DIR}/documentation/phrase-book.h ${CMAKE_CURRENT_SOURCE_DIR}/documentation/videothumbnails.h ) add_executable(kphotoalbum ${kphotoalbum_SRCS}) add_dependencies(kphotoalbum UpdateVersion) # External components target_link_libraries(kphotoalbum ${JPEG_LIBRARY} ${EXIV2_LIBRARIES} KF5::Archive KF5::Completion KF5::ConfigCore KF5::ConfigGui KF5::CoreAddons KF5::I18n KF5::IconThemes KF5::JobWidgets KF5::KIOCore KF5::KIOWidgets KF5::TextWidgets KF5::XmlGui KF5::WidgetsAddons Phonon::phonon4qt5 Qt5::Network Qt5::Sql ) if(KF5Kipi_FOUND) target_link_libraries(kphotoalbum KF5::Kipi) endif() if(KF5KDcraw_FOUND) target_link_libraries(kphotoalbum KF5::KDcraw) endif() if(KF5KGeoMap_FOUND) target_link_libraries(kphotoalbum KF5::KGeoMap ) endif() install(TARGETS kphotoalbum ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) ########### install files ############### install(PROGRAMS org.kde.kphotoalbum.desktop org.kde.kphotoalbum-import.desktop DESTINATION ${KDE_INSTALL_APPDIR}) install(FILES kphotoalbumrc DESTINATION ${KDE_INSTALL_CONFDIR}) install(FILES tips default-setup DESTINATION ${KDE_INSTALL_DATADIR}/kphotoalbum) install(FILES kphotoalbumui.rc DESTINATION ${KDE_INSTALL_KXMLGUI5DIR}/kphotoalbum) install(FILES org.kde.kphotoalbum.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) configure_file(config-kpa-kdcraw.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kpa-kdcraw.h) configure_file(config-kpa-kipi.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kpa-kipi.h) configure_file(config-kpa-kgeomap.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kpa-kgeomap.h) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) # vi:expandtab:tabstop=4 shiftwidth=4: diff --git a/ImageManager/ImageLoaderThread.cpp b/ImageManager/ImageLoaderThread.cpp index 4441dc9e..7816056d 100644 --- a/ImageManager/ImageLoaderThread.cpp +++ b/ImageManager/ImageLoaderThread.cpp @@ -1,158 +1,159 @@ /* Copyright (C) 2003-2010 Jesper K. Pedersen 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 2 of the License, or (at your option) 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ImageLoaderThread.h" #include "ThumbnailCache.h" #include "ImageDecoder.h" #include "AsyncLoader.h" #include "RawImageDecoder.h" +#include "Utilities/FastJpeg.h" #include "Utilities/Util.h" #include #include extern "C" { #include #include #include #include #include #include #include } #include #include #include "ImageEvent.h" namespace ImageManager { // Create a global instance. Its constructor will itself register it. RAWImageDecoder rawdecoder; } ImageManager::ImageLoaderThread::ImageLoaderThread( size_t bufsize ) : m_imageLoadBuffer( new char[bufsize] ), m_bufSize( bufsize ) { } ImageManager::ImageLoaderThread::~ImageLoaderThread() { delete[] m_imageLoadBuffer; } void ImageManager::ImageLoaderThread::run() { while ( true ) { ImageRequest* request = AsyncLoader::instance()->next(); Q_ASSERT( request ); if ( request->isExitRequest() ) { return; } bool ok; QImage img = loadImage( request, ok ); if ( ok ) { img = scaleAndRotate( request, img ); } request->setLoadedOK( ok ); ImageEvent* iew = new ImageEvent( request, img ); QApplication::postEvent( AsyncLoader::instance(), iew ); } } QImage ImageManager::ImageLoaderThread::loadImage( ImageRequest* request, bool& ok ) { int dim = calcLoadSize( request ); QSize fullSize; ok = false; if ( !request->fileSystemFileName().exists() ) return QImage(); QImage img; if (Utilities::isJPEG(request->fileSystemFileName())) { ok = Utilities::loadJPEG( &img, request->fileSystemFileName(), &fullSize, dim, m_imageLoadBuffer, m_bufSize ); if (ok == true) request->setFullSize( fullSize ); } else { // At first, we have to give our RAW decoders a try. If we allowed // QImage's load() method, it'd for example load a tiny thumbnail from // NEF files, which is not what we want. ok = ImageDecoder::decode( &img, request->fileSystemFileName(), &fullSize, dim); if (ok) request->setFullSize( img.size() ); } if (!ok) { // Now we can try QImage's stuff as a fallback... ok = img.load( request->fileSystemFileName().absolute() ); if (ok) request->setFullSize( img.size() ); } return img; } int ImageManager::ImageLoaderThread::calcLoadSize( ImageRequest* request ) { return qMax( request->width(), request->height() ); } QImage ImageManager::ImageLoaderThread::scaleAndRotate( ImageRequest* request, QImage img ) { if ( request->angle() != 0 ) { QMatrix matrix; matrix.rotate( request->angle() ); img = img.transformed( matrix ); int angle = (request->angle() + 360)%360; Q_ASSERT( angle >= 0 && angle <= 360 ); if ( angle == 90 || angle == 270 ) request->setFullSize( QSize( request->fullSize().height(), request->fullSize().width() ) ); } // If we are looking for a scaled version, then scale if ( shouldImageBeScale( img, request ) ) img = Utilities::scaleImage(img, request->width(), request->height(), Qt::KeepAspectRatio ); return img; } bool ImageManager::ImageLoaderThread::shouldImageBeScale( const QImage& img, ImageRequest* request ) { // No size specified, meaning we want it full size. if ( request->width() == -1 ) return false; if ( img.width() < request->width() && img.height() < request->height() ) { // The image is smaller than the requets. return request->doUpScale(); } return true; } // vi:expandtab:tabstop=4 shiftwidth=4: diff --git a/ImageManager/RawImageDecoder.cpp b/ImageManager/RawImageDecoder.cpp index 1de2c74b..b7825809 100644 --- a/ImageManager/RawImageDecoder.cpp +++ b/ImageManager/RawImageDecoder.cpp @@ -1,264 +1,264 @@ /* Copyright (C) 2003-2010 Jesper K. Pedersen 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 2 of the License, or (at your option) 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "RawImageDecoder.h" #include #include "Logging.h" #include #include "Settings/SettingsData.h" -#include +#include #include #include #ifdef HAVE_KDCRAW # include # include # include #endif namespace ImageManager { bool RAWImageDecoder::_decode( QImage *img, const DB::FileName& imageFile, QSize* fullSize, int dim) { /* width and height seem to be only hints, ignore */ Q_UNUSED( dim ); #ifdef HAVE_KDCRAW QByteArray previewData; if ( !KDcrawIface::KDcraw::loadEmbeddedPreview( previewData, imageFile.absolute() ) ) return false; // Faster than allowing loadRawPreview to do the decode itself if ( ! Utilities::loadJPEG(img, previewData, fullSize, dim ) ) return false; // FIXME: The preview data for Canon's image is always returned in its non-rotated form by libkdcraw, ie. KPA should do the rotation. // FIXME: This will happen later on. if ( Settings::SettingsData::instance()->useRawThumbnail() && ( ( dim > 0 && img->width() >= dim && img->height() >= dim ) || ( img->width() >= Settings::SettingsData::instance()->useRawThumbnailSize().width() && img->height() >= Settings::SettingsData::instance()->useRawThumbnailSize().height() ) ) ) return true; KDcrawIface::DcrawInfoContainer metadata; if (!KDcrawIface::KDcraw::rawFileIdentify(metadata, imageFile.absolute())) return false; if ((img->width() < metadata.imageSize.width() * 0.8) || (img->height() < metadata.imageSize.height() * 0.8)) { // let's try to get a better resolution KDcrawIface::KDcraw decoder; KDcrawIface::RawDecodingSettings rawDecodingSettings; if ( rawDecodingSettings.sixteenBitsImage ) { qCWarning(ImageManagerLog) << "16 bits per color channel is not supported yet"; return false; } else { QByteArray imageData; /* 3 bytes for each pixel, */ int width, height, rgbmax; if ( !decoder.decodeRAWImage( imageFile.absolute(), rawDecodingSettings, imageData, width, height, rgbmax ) ) return false; // Now the funny part, how to turn this fugly QByteArray into an QImage. Yay! *img = QImage(width, height, QImage::Format_RGB32); if (img->isNull()) return false; uchar* data = img->bits(); for ( int i = 0; i < imageData.size(); i += 3, data += 4 ) { data[0] = imageData[i + 2]; // blue data[1] = imageData[i + 1]; // green data[2] = imageData[i]; // red data[3] = 0xff; // alpha } } } if ( fullSize ) *fullSize = img->size(); return true; #else /* HAVE_KDCRAW */ Q_UNUSED( img ); Q_UNUSED( imageFile ); Q_UNUSED( fullSize ); return false; #endif /* HAVE_KDCRAW */ } void RAWImageDecoder::_initializeExtensionLists( QStringList& rawExtensions, QStringList& standardExtensions, QStringList& ignoredExtensions ) { static QStringList _rawExtensions, _standardExtensions, _ignoredExtensions; static bool extensionListsInitialized = false; if ( ! extensionListsInitialized ) { #ifdef HAVE_KDCRAW _rawExtensions = QString::fromLatin1( raw_file_extentions ).split( QChar::fromLatin1(' '), QString::SkipEmptyParts ); #endif /* HAVE_KDCRAW */ for (QStringList::iterator it = _rawExtensions.begin(); it != _rawExtensions.end(); ++it) (*it).remove(QString::fromUtf8("*.")); _standardExtensions << QString::fromLatin1("jpg") << QString::fromLatin1("JPG") << QString::fromLatin1("jpeg") << QString::fromLatin1("JPEG") << QString::fromLatin1("tif") << QString::fromLatin1("TIF") << QString::fromLatin1("tiff") << QString::fromLatin1("TIFF") << QString::fromLatin1("png") << QString::fromLatin1("PNG"); _ignoredExtensions << QString::fromLatin1("thm") // Thumbnails << QString::fromLatin1("THM") << QString::fromLatin1("thumb") // thumbnail files // from dcraw << QString::fromLatin1("ctg") // Catalog files << QString::fromLatin1("gz") // Compressed files << QString::fromLatin1("Z") << QString::fromLatin1("bz2") << QString::fromLatin1("zip") << QString::fromLatin1("xml") << QString::fromLatin1("XML") << QString::fromLatin1("html") << QString::fromLatin1("HTML") << QString::fromLatin1("htm") << QString::fromLatin1("HTM") << QString::fromLatin1("pp3") // RawTherapee Sidecar files << QString::fromLatin1("PP3") << QString::fromLatin1("xmp") // Other sidecars << QString::fromLatin1("XMP") << QString::fromLatin1("pto") // Hugin sidecars << QString::fromLatin1("PTO"); QChar dot( QChar::fromLatin1('.') ); for ( QStringList::iterator it = _rawExtensions.begin(); it != _rawExtensions.end(); ++it ) if ( !(*it).startsWith( dot) ) *it = dot + *it; for ( QStringList::iterator it = _standardExtensions.begin(); it != _standardExtensions.end(); ++it ) if ( !(*it).startsWith( dot) ) *it = dot + *it; for ( QStringList::iterator it = _ignoredExtensions.begin(); it != _ignoredExtensions.end(); ++it ) if ( !(*it).startsWith( dot) ) *it = dot + *it; extensionListsInitialized = true; } rawExtensions = _rawExtensions; standardExtensions = _standardExtensions; ignoredExtensions = _ignoredExtensions; } bool RAWImageDecoder::_fileExistsWithExtensions( const DB::FileName& fileName, const QStringList& extensionList) const { QString baseFileName = fileName.absolute(); int extStart = baseFileName.lastIndexOf(QChar::fromLatin1('.')); // We're interested in xxx.yyy, not .yyy if (extStart <= 1) return false; baseFileName.remove(extStart, baseFileName.length() - extStart); for ( QStringList::ConstIterator it = extensionList.begin(); it != extensionList.end(); ++it ) { if (QFile::exists(baseFileName + *it)) return true; } return false; } bool RAWImageDecoder::_fileIsKnownWithExtensions( const DB::FileNameSet& files, const DB::FileName& fileName, const QStringList& extensionList) const { QString baseFileName = fileName.absolute(); int extStart = baseFileName.lastIndexOf(QChar::fromLatin1('.')); if (extStart <= 1) return false; baseFileName.remove(extStart, baseFileName.length() - extStart); for ( QStringList::ConstIterator it = extensionList.begin(); it != extensionList.end(); ++it ) { if (files.contains(DB::FileName::fromAbsolutePath(baseFileName + *it)) ) return true; } return false; } bool RAWImageDecoder::_fileEndsWithExtensions( const DB::FileName& fileName, const QStringList& extensionList) { for ( QStringList::ConstIterator it = extensionList.begin(); it != extensionList.end(); ++it ) { if( fileName.relative().endsWith( *it, Qt::CaseInsensitive ) ) return true; } return false; } bool RAWImageDecoder::_mightDecode( const DB::FileName& imageFile ) { QStringList _rawExtensions, _standardExtensions, _ignoredExtensions; _initializeExtensionLists( _rawExtensions, _standardExtensions, _ignoredExtensions ); if (Settings::SettingsData::instance()->skipRawIfOtherMatches() && _fileExistsWithExtensions(imageFile, _standardExtensions)) return false; if (_fileEndsWithExtensions(imageFile, _rawExtensions)) return true; return false; } bool RAWImageDecoder::isRAW( const DB::FileName& imageFile ) { QStringList _rawExtensions, _standardExtensions, _ignoredExtensions; _initializeExtensionLists( _rawExtensions, _standardExtensions, _ignoredExtensions ); return _fileEndsWithExtensions(imageFile, _rawExtensions); } QStringList RAWImageDecoder::rawExtensions() { QStringList _rawExtensions, _standardExtensions, _ignoredExtensions; _initializeExtensionLists( _rawExtensions, _standardExtensions, _ignoredExtensions ); return _rawExtensions; } bool RAWImageDecoder::_skipThisFile( const DB::FileNameSet& loadedFiles, const DB::FileName& imageFile ) const { QStringList _rawExtensions, _standardExtensions, _ignoredExtensions; _initializeExtensionLists( _rawExtensions, _standardExtensions, _ignoredExtensions ); // We're not interested in thumbnail and other files. if (_fileEndsWithExtensions(imageFile, _ignoredExtensions)) return true; // If we *are* interested in raw files even when other equivalent // non-raw files are available, then we're interested in this file. if (! (Settings::SettingsData::instance()->skipRawIfOtherMatches())) return false; // If the file ends with something other than a known raw extension, // we're interested in it. if (! _fileEndsWithExtensions(imageFile, _rawExtensions)) return false; // At this point, the file ends with a known raw extension, and we're // not interested in raw files when other non-raw files are available. // So search for an existing file with one of the standard // extensions. // // This may not be the best way to do this, but it's using the // same algorithm as _mightDecode above. // -- Robert Krawitz rlk@alum.mit.edu 2007-07-22 return _fileIsKnownWithExtensions(loadedFiles, imageFile, _standardExtensions); } } // vi:expandtab:tabstop=4 shiftwidth=4: diff --git a/Utilities/FastJpeg.cpp b/Utilities/FastJpeg.cpp new file mode 100644 index 00000000..c7004769 --- /dev/null +++ b/Utilities/FastJpeg.cpp @@ -0,0 +1,207 @@ +/* Copyright (C) 2003-2010 Jesper K. Pedersen + + 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 2 of the License, or (at your option) 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "FastJpeg.h" + +#include "Logging.h" +#include "JpeglibWithFix.h" + +#include "DB/FileName.h" + +#include +#include +#include + +#include +extern "C" { +#include +#include +#include +#include +} + +struct myjpeg_error_mgr : public jpeg_error_mgr +{ + jmp_buf setjmp_buffer; +}; + +extern "C" +{ + static void myjpeg_error_exit(j_common_ptr cinfo) + { + auto* myerr = (myjpeg_error_mgr*) cinfo->err; + + char buffer[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message)(cinfo, buffer); + //kWarning() << buffer; + longjmp(myerr->setjmp_buffer, 1); + } +} + +namespace Utilities +{ + static bool loadJPEGInternal(QImage *img, FILE* inputFile, QSize* fullSize, int dim, const char *membuf, size_t membuf_size ); +} + +bool Utilities::loadJPEG(QImage *img, const DB::FileName& imageFile, QSize* fullSize, int dim, char *membuf, size_t membufSize) +{ + bool ok; + struct stat statbuf; + if ( stat( QFile::encodeName(imageFile.absolute()).constData(), &statbuf ) == -1 ) + return false; + if ( ! membuf || statbuf.st_size > (int) membufSize ) { + qCDebug(UtilitiesLog) << "loadJPEG (slow path) " << imageFile.relative() << " " << statbuf.st_size << " " << membufSize; + FILE* inputFile=fopen( QFile::encodeName(imageFile.absolute()).constData(), "rb"); + if(!inputFile) + return false; + ok = loadJPEGInternal( img, inputFile, fullSize, dim, NULL, 0 ); + fclose(inputFile); + } else { + // May be more than is needed, but less likely to fragment memory this + // way. + int inputFD = open( QFile::encodeName(imageFile.absolute()).constData(), O_RDONLY ); + if ( inputFD == -1 ) { + return false; + } + unsigned bytesLeft = statbuf.st_size; + unsigned offset = 0; + while ( bytesLeft > 0 ) { + int bytes = read(inputFD, membuf + offset, bytesLeft); + if (bytes <= 0) { + (void) close(inputFD); + return false; + } + offset += bytes; + bytesLeft -= bytes; + } + ok = loadJPEGInternal( img, NULL, fullSize, dim, membuf, statbuf.st_size ); + (void) close(inputFD); + } + return ok; +} + +bool Utilities::loadJPEG(QImage *img, const DB::FileName& imageFile, QSize* fullSize, int dim) +{ + return loadJPEG( img, imageFile, fullSize, dim, NULL, 0 ); +} + +bool Utilities::loadJPEG(QImage *img, const QByteArray &data, QSize* fullSize, int dim) +{ + return loadJPEGInternal(img, nullptr, fullSize, dim, data.data(), data.size()); +} + +bool Utilities::loadJPEGInternal(QImage *img, FILE* inputFile, QSize* fullSize, int dim, const char *membuf, size_t membuf_size ) +{ + struct jpeg_decompress_struct cinfo; + struct myjpeg_error_mgr jerr; + + // JPEG error handling - thanks to Marcus Meissner + cinfo.err = jpeg_std_error(&jerr); + cinfo.err->error_exit = myjpeg_error_exit; + + if (setjmp(jerr.setjmp_buffer)) { + jpeg_destroy_decompress(&cinfo); + return false; + } + + jpeg_create_decompress(&cinfo); + if (inputFile) + jpeg_stdio_src(&cinfo, inputFile); + else + jpeg_mem_src(&cinfo, (unsigned char *) membuf, membuf_size); + jpeg_read_header(&cinfo, TRUE); + *fullSize = QSize( cinfo.image_width, cinfo.image_height ); + + int imgSize = qMax(cinfo.image_width, cinfo.image_height); + + //libjpeg supports a sort of scale-while-decoding which speeds up decoding + int scale=1; + if (dim != -1) { + while(dim*scale*2<=imgSize) { + scale*=2; + } + if(scale>8) scale=8; + } + + cinfo.scale_num=1; + cinfo.scale_denom=scale; + + // Create QImage + jpeg_start_decompress(&cinfo); + + switch(cinfo.output_components) { + case 3: + case 4: + *img = QImage( + cinfo.output_width, cinfo.output_height, QImage::Format_RGB32); + if (img->isNull()) + return false; + break; + case 1: // B&W image + *img = QImage( + cinfo.output_width, cinfo.output_height, QImage::Format_Indexed8); + if (img->isNull()) + return false; + img->setColorCount(256); + for (int i=0; i<256; i++) + img->setColor(i, qRgb(i,i,i)); + break; + default: + return false; + } + + QVector linesVector; + linesVector.reserve(img->height()); + for (int i = 0; i < img->height(); ++i) + linesVector.push_back(img->scanLine(i)); + uchar** lines = linesVector.data(); + while (cinfo.output_scanline < cinfo.output_height) + jpeg_read_scanlines(&cinfo, lines + cinfo.output_scanline, + cinfo.output_height); + jpeg_finish_decompress(&cinfo); + + // Expand 24->32 bpp + if ( cinfo.output_components == 3 ) { + for (uint j=0; jscanLine(j) + cinfo.output_width*3; + QRgb *out = reinterpret_cast( img->scanLine(j) ); + + for (uint i=cinfo.output_width; i--; ) { + in-=3; + out[i] = qRgb(in[0], in[1], in[2]); + } + } + } + + /*int newMax = qMax(cinfo.output_width, cinfo.output_height); + int newx = size_*cinfo.output_width / newMax; + int newy = size_*cinfo.output_height / newMax;*/ + + jpeg_destroy_decompress(&cinfo); + + //image = img.smoothScale(newx,newy); + return true; +} + +bool Utilities::isJPEG( const DB::FileName& fileName ) +{ + QString format= QString::fromLocal8Bit( QImageReader::imageFormat( fileName.absolute() ) ); + return format == QString::fromLocal8Bit( "jpeg" ); +} + +// vi:expandtab:tabstop=4 shiftwidth=4: diff --git a/Utilities/FastJpeg.h b/Utilities/FastJpeg.h new file mode 100644 index 00000000..b4a084c2 --- /dev/null +++ b/Utilities/FastJpeg.h @@ -0,0 +1,39 @@ +/* Copyright (C) 2003-2010 Jesper K. Pedersen + + 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 2 of the License, or (at your option) 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; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef FASTJPEG_H +#define FASTJPEG_H +#include +#include + +namespace DB +{ +class FileName; +} + +namespace Utilities +{ +bool loadJPEG(QImage *img, const DB::FileName& imageFile, QSize* fullSize, int dim=-1); +bool loadJPEG(QImage *img, const DB::FileName& imageFile, QSize* fullSize, int dim=-1, char *membuf=NULL, size_t membufSize=0); +bool loadJPEG(QImage *img, const QByteArray& data, QSize* fullSize, int dim=-1); +bool isJPEG( const DB::FileName& fileName ); +} + +#endif /* FASTJPEG_H */ + +// vi:expandtab:tabstop=4 shiftwidth=4: diff --git a/Utilities/Util.cpp b/Utilities/Util.cpp index 22fa69d6..68fda27e 100644 --- a/Utilities/Util.cpp +++ b/Utilities/Util.cpp @@ -1,854 +1,682 @@ /* Copyright (C) 2003-2010 Jesper K. Pedersen 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 2 of the License, or (at your option) 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "Util.h" #include "Logging.h" #include #include #include #include #include #include #include #include -#include "JpeglibWithFix.h" - #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern "C" { #include #include #include #include #include #include #include #include } namespace { // Determined experimentally to yield best results (on Seagate 2TB 2.5" disk, // 5400 RPM). Performance is very similar at 524288. Above that, performance // was significantly worse. Below that, performance also deteriorated. // This assumes use of one image scout thread (see DB/ImageScout.cpp). Without // a scout thread, performance was about 10-15% worse. constexpr int MD5_BUFFER_SIZE = 262144; } /** * Add a line label + info text to the result text if info is not empty. * If the result already contains something, a HTML newline is added first. * To be used in createInfoText(). */ static void AddNonEmptyInfo(const QString &label, const QString &info, QString *result) { if (info.isEmpty()) return; if (!result->isEmpty()) *result += QString::fromLatin1("
"); result->append(label).append(info); } /** * Given an ImageInfoPtr this function will create an HTML blob about the * image. The blob is used in the viewer and in the tool tip box from the * thumbnail view. * * As the HTML text is created, the parameter linkMap is filled with * information about hyperlinks. The map maps from an index to a pair of * (categoryName, categoryItem). This linkMap is used when the user selects * one of the hyberlinks. */ QString Utilities::createInfoText( DB::ImageInfoPtr info, QMap< int,QPair >* linkMap ) { Q_ASSERT( info ); QString result; if ( Settings::SettingsData::instance()->showFilename() ) { AddNonEmptyInfo(i18n("File Name: "), info->fileName().relative(), &result); } if ( Settings::SettingsData::instance()->showDate() ) { AddNonEmptyInfo(i18n("Date: "), info->date().toString( Settings::SettingsData::instance()->showTime() ? true : false ), &result); } /* XXX */ if ( Settings::SettingsData::instance()->showImageSize() && info->mediaType() == DB::Image) { const QSize imageSize = info->size(); // Do not add -1 x -1 text if (imageSize.width() >= 0 && imageSize.height() >= 0) { const double megapix = imageSize.width() * imageSize.height() / 1000000.0; QString info = i18nc("width x height","%1x%2" ,QString::number(imageSize.width()) ,QString::number(imageSize.height())); if (megapix > 0.05) { info += i18nc("short for: x megapixels"," (%1MP)" ,QString::number(megapix, 'f', 1)); } const double aspect = (double) imageSize.width() / (double) imageSize.height(); if (aspect > 1) info += i18nc("aspect ratio"," (%1:1)" ,QLocale::system().toString(aspect, 'f', 2)); else if (aspect >= 0.995 && aspect < 1.005) info += i18nc("aspect ratio"," (1:1)"); else info += i18nc("aspect ratio"," (1:%1)" ,QLocale::system().toString(1.0/aspect, 'f', 2)); AddNonEmptyInfo(i18n("Image Size: "), info, &result); } } if ( Settings::SettingsData::instance()->showRating() ) { if ( info->rating() != -1 ) { if ( ! result.isEmpty() ) result += QString::fromLatin1("
"); QUrl rating; rating.setScheme(QString::fromLatin1("kratingwidget")); // we don't use the host part, but if we don't set it, we can't use port: rating.setHost(QString::fromLatin1("int")); rating.setPort(qMin( qMax( static_cast(0), info->rating() ), static_cast(10))); result += QString::fromLatin1("").arg( rating.toString(QUrl::None) ); } } QList categories = DB::ImageDB::instance()->categoryCollection()->categories(); int link = 0; Q_FOREACH( const DB::CategoryPtr category, categories ) { const QString categoryName = category->name(); if ( category->doShow() ) { StringSet items = info->itemsOfCategory( categoryName ); if (Settings::SettingsData::instance()->hasUntaggedCategoryFeatureConfigured() && ! Settings::SettingsData::instance()->untaggedImagesTagVisible()) { if (categoryName == Settings::SettingsData::instance()->untaggedCategory()) { if (items.contains(Settings::SettingsData::instance()->untaggedTag())) { items.remove(Settings::SettingsData::instance()->untaggedTag()); } } } if (!items.empty()) { QString title = QString::fromUtf8("%1: ").arg(category->name()); QString infoText; bool first = true; Q_FOREACH( const QString &item, items) { if ( first ) first = false; else infoText += QString::fromLatin1( ", " ); if ( linkMap ) { ++link; (*linkMap)[link] = QPair( categoryName, item ); infoText += QString::fromLatin1( "%2").arg( link ).arg( item ); infoText += formatAge(category, item, info); } else infoText += item; } AddNonEmptyInfo(title, infoText, &result); } } } if ( Settings::SettingsData::instance()->showLabel()) { AddNonEmptyInfo(i18n("Label: "), info->label(), &result); } if ( Settings::SettingsData::instance()->showDescription() && !info->description().trimmed().isEmpty() ) { AddNonEmptyInfo(i18n("Description: "), info->description(), &result); } QString exifText; if ( Settings::SettingsData::instance()->showEXIF() ) { typedef QMap ExifMap; typedef ExifMap::const_iterator ExifMapIterator; ExifMap exifMap = Exif::Info::instance()->infoForViewer( info->fileName(), Settings::SettingsData::instance()->iptcCharset() ); for( ExifMapIterator exifIt = exifMap.constBegin(); exifIt != exifMap.constEnd(); ++exifIt ) { if ( exifIt.key().startsWith( QString::fromLatin1( "Exif." ) ) ) for ( QStringList::const_iterator valuesIt = exifIt.value().constBegin(); valuesIt != exifIt.value().constEnd(); ++valuesIt ) { QString exifName = exifIt.key().split( QChar::fromLatin1('.') ).last(); AddNonEmptyInfo(QString::fromLatin1( "%1: ").arg(exifName), *valuesIt, &exifText); } } QString iptcText; for( ExifMapIterator exifIt = exifMap.constBegin(); exifIt != exifMap.constEnd(); ++exifIt ) { if ( !exifIt.key().startsWith( QString::fromLatin1( "Exif." ) ) ) for ( QStringList::const_iterator valuesIt = exifIt.value().constBegin(); valuesIt != exifIt.value().constEnd(); ++valuesIt ) { QString iptcName = exifIt.key().split( QChar::fromLatin1('.') ).last(); AddNonEmptyInfo(QString::fromLatin1( "%1: ").arg(iptcName), *valuesIt, &iptcText); } } if ( !iptcText.isEmpty() ) { if ( exifText.isEmpty() ) exifText = iptcText; else exifText += QString::fromLatin1( "
" ) + iptcText; } } if ( !result.isEmpty() && !exifText.isEmpty() ) result += QString::fromLatin1( "
" ); result += exifText; return result; } using DateSpec = QPair; DateSpec dateDiff(const QDate& birthDate, const QDate& imageDate) { const int bday = birthDate.day(); const int iday = imageDate.day(); const int bmonth = birthDate.month(); const int imonth = imageDate.month(); const int byear = birthDate.year(); const int iyear = imageDate.year(); // Image before birth const int diff = birthDate.daysTo(imageDate); if (diff < 0) return qMakePair(0, 'I'); if (diff < 31) return qMakePair(diff, 'D'); int months = (iyear-byear)*12; months += (imonth-bmonth); months += (iday >= bday) ? 0 : -1; if ( months < 24) return qMakePair(months, 'M'); else return qMakePair(months/12, 'Y'); } QString formatDate(const DateSpec& date) { if (date.second == 'I') return {}; else if (date.second == 'D') return i18np("1 day", "%1 days", date.first); else if (date.second == 'M') return i18np("1 month", "%1 months", date.first); else return i18np("1 year", "%1 years", date.first); } void test() { Q_ASSERT(formatDate(dateDiff(QDate(1971,7,11), QDate(1971,7,11))) == QString::fromLatin1("0 days")); Q_ASSERT(formatDate(dateDiff(QDate(1971,7,11), QDate(1971,8,10))) == QString::fromLatin1("30 days")); Q_ASSERT(formatDate(dateDiff(QDate(1971,7,11), QDate(1971,8,11))) == QString::fromLatin1("1 month")); Q_ASSERT(formatDate(dateDiff(QDate(1971,7,11), QDate(1971,8,12))) == QString::fromLatin1("1 month")); Q_ASSERT(formatDate(dateDiff(QDate(1971,7,11), QDate(1971,9,10))) == QString::fromLatin1("1 month")); Q_ASSERT(formatDate(dateDiff(QDate(1971,7,11), QDate(1971,9,11))) == QString::fromLatin1("2 month")); Q_ASSERT(formatDate(dateDiff(QDate(1971,7,11), QDate(1972,6,10))) == QString::fromLatin1("10 month")); Q_ASSERT(formatDate(dateDiff(QDate(1971,7,11), QDate(1972,6,11))) == QString::fromLatin1("11 month")); Q_ASSERT(formatDate(dateDiff(QDate(1971,7,11), QDate(1972,6,12))) == QString::fromLatin1("11 month")); Q_ASSERT(formatDate(dateDiff(QDate(1971,7,11), QDate(1972,7,10))) == QString::fromLatin1("11 month")); Q_ASSERT(formatDate(dateDiff(QDate(1971,7,11), QDate(1972,7,11))) == QString::fromLatin1("12 month")); Q_ASSERT(formatDate(dateDiff(QDate(1971,7,11), QDate(1972,7,12))) == QString::fromLatin1("12 month")); Q_ASSERT(formatDate(dateDiff(QDate(1971,7,11), QDate(1972,12,11))) == QString::fromLatin1("17 month")); Q_ASSERT(formatDate(dateDiff(QDate(1971,7,11), QDate(1973,7,11))) == QString::fromLatin1("2 years")); } QString Utilities::formatAge(DB::CategoryPtr category, const QString &item, DB::ImageInfoPtr info) { // test(); // I wish I could get my act together to set up a test suite. const QDate birthDate = category->birthDate(item); const QDate start = info->date().start().date(); const QDate end = info->date().end().date(); if (birthDate.isNull() || start.isNull()) return {}; if ( start == end) return QString::fromUtf8(" (%1)").arg(formatDate(dateDiff(birthDate, start))); else { DateSpec lower = dateDiff(birthDate,start); DateSpec upper = dateDiff(birthDate,end); if (lower == upper) return QString::fromUtf8(" (%1)").arg(formatDate(lower)); else if (lower.second == 'I') return QString::fromUtf8(" (< %1)").arg(formatDate(upper)); else { if (lower.second == upper.second) return QString::fromUtf8(" (%1-%2)").arg(lower.first).arg(formatDate(upper)); else return QString::fromUtf8(" (%1-%2)").arg(formatDate(lower)).arg(formatDate(upper)); } } } void Utilities::checkForBackupFile( const QString& fileName, const QString& message ) { QString backupName = QFileInfo( fileName ).absolutePath() + QString::fromLatin1("/.#") + QFileInfo( fileName ).fileName(); QFileInfo backUpFile( backupName); QFileInfo indexFile( fileName ); if ( !backUpFile.exists() || indexFile.lastModified() > backUpFile.lastModified() || backUpFile.size() == 0 ) if ( !( backUpFile.exists() && !message.isNull() ) ) return; int code; if ( message.isNull() ) code = KMessageBox::questionYesNo( nullptr, i18n("Autosave file '%1' exists (size %3 KB) and is newer than '%2'. " "Should the autosave file be used?", backupName, fileName, backUpFile.size() >> 10 ), i18n("Found Autosave File") ); else if ( backUpFile.size() > 0 ) code = KMessageBox::warningYesNo( nullptr,i18n( "

Error: Cannot use current database file '%1':

%2

" "

Do you want to use autosave (%3 - size %4 KB) instead of exiting?

" "

(Manually verifying and copying the file might be a good idea)

", fileName, message, backupName, backUpFile.size() >> 10 ), i18n("Recover from Autosave?") ); else { KMessageBox::error( nullptr, i18n( "

Error: %1

Also autosave file is empty, check manually " "if numbered backup files exist and can be used to restore index.xml.

", message ) ); exit(-1); } if ( code == KMessageBox::Yes ) { QFile in( backupName ); if ( in.open( QIODevice::ReadOnly ) ) { QFile out( fileName ); if (out.open( QIODevice::WriteOnly ) ) { char data[1024]; int len; while ( (len = in.read( data, 1024 ) ) ) out.write( data, len ); } } } else if ( !message.isNull() ) exit(-1); } bool Utilities::ctrlKeyDown() { return QApplication::keyboardModifiers() & Qt::ControlModifier; } void Utilities::copyList( const QStringList& from, const QString& directoryTo ) { for( QStringList::ConstIterator it = from.constBegin(); it != from.constEnd(); ++it ) { QString destFile = directoryTo + QString::fromLatin1( "/" ) + QFileInfo(*it).fileName(); if ( ! QFileInfo( destFile ).exists() ) { const bool ok = copy( *it, destFile ); if ( !ok ) { KMessageBox::error( nullptr, i18n("Unable to copy '%1' to '%2'.", *it , destFile ), i18n("Error Running Demo") ); exit(-1); } } } } QString Utilities::setupDemo() { QString demoDir = QString::fromLatin1( "%1/kphotoalbum-demo-%2" ).arg(QDir::tempPath()).arg(QString::fromLocal8Bit( qgetenv( "LOGNAME" ) )); QFileInfo fi(demoDir); if ( ! fi.exists() ) { bool ok = QDir().mkdir( demoDir ); if ( !ok ) { KMessageBox::error( nullptr, i18n("Unable to create directory '%1' needed for demo.", demoDir ), i18n("Error Running Demo") ); exit(-1); } } // index.xml QString demoDB = locateDataFile(QString::fromLatin1("demo/index.xml")); if ( demoDB.isEmpty() ) { qCDebug(UtilitiesLog) << "No demo database in standard locations:" << QStandardPaths::standardLocations(QStandardPaths::DataLocation); exit(-1); } QString configFile = demoDir + QString::fromLatin1( "/index.xml" ); copy(demoDB, configFile); // Images const QStringList kpaDemoDirs = QStandardPaths::locateAll( QStandardPaths::DataLocation, QString::fromLatin1("demo"), QStandardPaths::LocateDirectory); QStringList images; Q_FOREACH(const QString &dir, kpaDemoDirs) { QDirIterator it(dir, QStringList() << QStringLiteral("*.jpg") << QStringLiteral("*.avi")); while (it.hasNext()) { images.append(it.next()); } } copyList( images, demoDir ); // CategoryImages QString catDir = demoDir + QString::fromLatin1("/CategoryImages"); fi = QFileInfo(catDir); if ( ! fi.exists() ) { bool ok = QDir().mkdir( catDir ); if ( !ok ) { KMessageBox::error( nullptr, i18n("Unable to create directory '%1' needed for demo.", catDir ), i18n("Error Running Demo") ); exit(-1); } } const QStringList kpaDemoCatDirs = QStandardPaths::locateAll( QStandardPaths::DataLocation, QString::fromLatin1("demo/CategoryImages"), QStandardPaths::LocateDirectory); QStringList catImages; Q_FOREACH(const QString &dir, kpaDemoCatDirs) { QDirIterator it(dir, QStringList() << QStringLiteral("*.jpg")); while (it.hasNext()) { catImages.append(it.next()); } } copyList( catImages, catDir ); return configFile; } bool Utilities::copy( const QString& from, const QString& to ) { if ( QFileInfo(to).exists()) QDir().remove(to); return QFile::copy(from,to); } bool Utilities::makeHardLink( const QString& from, const QString& to ) { if (link(from.toLocal8Bit().constData(), to.toLocal8Bit().constData()) != 0) return false; else return true; } bool Utilities::makeSymbolicLink( const QString& from, const QString& to ) { if (symlink(from.toLocal8Bit().constData(), to.toLocal8Bit().constData()) != 0) return false; else return true; } bool Utilities::canReadImage( const DB::FileName& fileName ) { bool fastMode = !Settings::SettingsData::instance()->ignoreFileExtension(); QMimeDatabase::MatchMode mode = fastMode ? QMimeDatabase::MatchExtension : QMimeDatabase::MatchDefault; QMimeDatabase db; QMimeType mimeType = db.mimeTypeForFile( fileName.absolute(), mode ); return QImageReader::supportedMimeTypes().contains( mimeType.name().toUtf8() ) || ImageManager::ImageDecoder::mightDecode( fileName ); } QString Utilities::locateDataFile(const QString& fileName) { return QStandardPaths::locate(QStandardPaths::DataLocation, fileName); } QString Utilities::readFile( const QString& fileName ) { if ( fileName.isEmpty() ) { KMessageBox::error( nullptr, i18n("

No file name given!

") ); return QString(); } QFile file( fileName ); if ( !file.open( QIODevice::ReadOnly ) ) { //KMessageBox::error( nullptr, i18n("Could not open file %1").arg( fileName ) ); return QString(); } QTextStream stream( &file ); QString content = stream.readAll(); file.close(); return content; } -struct myjpeg_error_mgr : public jpeg_error_mgr -{ - jmp_buf setjmp_buffer; -}; - -extern "C" -{ - static void myjpeg_error_exit(j_common_ptr cinfo) - { - myjpeg_error_mgr* myerr = - (myjpeg_error_mgr*) cinfo->err; - - char buffer[JMSG_LENGTH_MAX]; - (*cinfo->err->format_message)(cinfo, buffer); - //kWarning() << buffer; - longjmp(myerr->setjmp_buffer, 1); - } -} - -namespace Utilities -{ - static bool loadJPEGInternal(QImage *img, FILE* inputFile, QSize* fullSize, int dim, const char *membuf, size_t membuf_size ); -} - -bool Utilities::loadJPEG(QImage *img, const DB::FileName& imageFile, QSize* fullSize, int dim, char *membuf, size_t membufSize) -{ - bool ok; - struct stat statbuf; - if ( stat( QFile::encodeName(imageFile.absolute()).constData(), &statbuf ) == -1 ) - return false; - if ( ! membuf || statbuf.st_size > (int) membufSize ) { - qCDebug(UtilitiesLog) << "loadJPEG (slow path) " << imageFile.relative() << " " << statbuf.st_size << " " << membufSize; - FILE* inputFile=fopen( QFile::encodeName(imageFile.absolute()).constData(), "rb"); - if(!inputFile) - return false; - ok = loadJPEGInternal( img, inputFile, fullSize, dim, NULL, 0 ); - fclose(inputFile); - } else { - // May be more than is needed, but less likely to fragment memory this - // way. - int inputFD = open( QFile::encodeName(imageFile.absolute()).constData(), O_RDONLY ); - if ( inputFD == -1 ) { - return false; - } - unsigned bytesLeft = statbuf.st_size; - unsigned offset = 0; - while ( bytesLeft > 0 ) { - int bytes = read(inputFD, membuf + offset, bytesLeft); - if (bytes <= 0) { - (void) close(inputFD); - return false; - } - offset += bytes; - bytesLeft -= bytes; - } - ok = loadJPEGInternal( img, NULL, fullSize, dim, membuf, statbuf.st_size ); - (void) close(inputFD); - } - return ok; -} - -bool Utilities::loadJPEG(QImage *img, const DB::FileName& imageFile, QSize* fullSize, int dim) -{ - return loadJPEG( img, imageFile, fullSize, dim, NULL, 0 ); -} - -bool Utilities::loadJPEG(QImage *img, const QByteArray &data, QSize* fullSize, int dim) -{ - return loadJPEGInternal(img, nullptr, fullSize, dim, data.data(), data.size()); -} - -bool Utilities::loadJPEGInternal(QImage *img, FILE* inputFile, QSize* fullSize, int dim, const char *membuf, size_t membuf_size ) -{ - struct jpeg_decompress_struct cinfo; - struct myjpeg_error_mgr jerr; - - // JPEG error handling - thanks to Marcus Meissner - cinfo.err = jpeg_std_error(&jerr); - cinfo.err->error_exit = myjpeg_error_exit; - - if (setjmp(jerr.setjmp_buffer)) { - jpeg_destroy_decompress(&cinfo); - return false; - } - - jpeg_create_decompress(&cinfo); - if (inputFile) - jpeg_stdio_src(&cinfo, inputFile); - else - jpeg_mem_src(&cinfo, (unsigned char *) membuf, membuf_size); - jpeg_read_header(&cinfo, TRUE); - *fullSize = QSize( cinfo.image_width, cinfo.image_height ); - - int imgSize = qMax(cinfo.image_width, cinfo.image_height); - - //libjpeg supports a sort of scale-while-decoding which speeds up decoding - int scale=1; - if (dim != -1) { - while(dim*scale*2<=imgSize) { - scale*=2; - } - if(scale>8) scale=8; - } - - cinfo.scale_num=1; - cinfo.scale_denom=scale; - - // Create QImage - jpeg_start_decompress(&cinfo); - - switch(cinfo.output_components) { - case 3: - case 4: - *img = QImage( - cinfo.output_width, cinfo.output_height, QImage::Format_RGB32); - if (img->isNull()) - return false; - break; - case 1: // B&W image - *img = QImage( - cinfo.output_width, cinfo.output_height, QImage::Format_Indexed8); - if (img->isNull()) - return false; - img->setColorCount(256); - for (int i=0; i<256; i++) - img->setColor(i, qRgb(i,i,i)); - break; - default: - return false; - } - - QVector linesVector; - linesVector.reserve(img->height()); - for (int i = 0; i < img->height(); ++i) - linesVector.push_back(img->scanLine(i)); - uchar** lines = linesVector.data(); - while (cinfo.output_scanline < cinfo.output_height) - jpeg_read_scanlines(&cinfo, lines + cinfo.output_scanline, - cinfo.output_height); - jpeg_finish_decompress(&cinfo); - - // Expand 24->32 bpp - if ( cinfo.output_components == 3 ) { - for (uint j=0; jscanLine(j) + cinfo.output_width*3; - QRgb *out = reinterpret_cast( img->scanLine(j) ); - - for (uint i=cinfo.output_width; i--; ) { - in-=3; - out[i] = qRgb(in[0], in[1], in[2]); - } - } - } - - /*int newMax = qMax(cinfo.output_width, cinfo.output_height); - int newx = size_*cinfo.output_width / newMax; - int newy = size_*cinfo.output_height / newMax;*/ - - jpeg_destroy_decompress(&cinfo); - - //image = img.smoothScale(newx,newy); - return true; -} - -bool Utilities::isJPEG( const DB::FileName& fileName ) -{ - QString format= QString::fromLocal8Bit( QImageReader::imageFormat( fileName.absolute() ) ); - return format == QString::fromLocal8Bit( "jpeg" ); -} - namespace Utilities { QString normalizedFileName( const QString& fileName ) { return QFileInfo(fileName).absoluteFilePath(); } QString dereferenceSymLinks( const QString& fileName ) { QFileInfo fi(fileName); int rounds = 256; while (fi.isSymLink() && --rounds > 0) fi = QFileInfo(fi.readLink()); if (rounds == 0) return QString(); return fi.filePath(); } } QString Utilities::stripEndingForwardSlash( const QString& fileName ) { static QString slash = QString::fromLatin1("/"); if ( fileName.endsWith( slash ) ) return fileName.left( fileName.length()-1); else return fileName; } QString Utilities::relativeFolderName( const QString& fileName) { int index= fileName.lastIndexOf( QChar::fromLatin1('/'), -1); if (index == -1) return QString(); else return fileName.left( index ); } void Utilities::deleteDemo() { QString dir = QString::fromLatin1( "%1/kphotoalbum-demo-%2" ).arg(QDir::tempPath()).arg(QString::fromLocal8Bit( qgetenv( "LOGNAME" ) ) ); QUrl demoUrl = QUrl::fromLocalFile( dir ); KJob *delDemoJob = KIO::del( demoUrl ); KJobWidgets::setWindow( delDemoJob, MainWindow::Window::theMainWindow()); delDemoJob->exec(); } QString Utilities::absoluteImageFileName( const QString& relativeName ) { return stripEndingForwardSlash( Settings::SettingsData::instance()->imageDirectory() ) + QString::fromLatin1( "/" ) + relativeName; } QString Utilities::imageFileNameToAbsolute( const QString& fileName ) { if ( fileName.startsWith( Settings::SettingsData::instance()->imageDirectory() ) ) return fileName; else if ( fileName.startsWith( QString::fromLatin1("file://") ) ) return imageFileNameToAbsolute( fileName.mid( 7 ) ); // 7 == length("file://") else if ( fileName.startsWith( QString::fromLatin1("/") ) ) return QString(); // Not within our image root else return absoluteImageFileName( fileName ); } bool operator>( const QPoint& p1, const QPoint& p2) { return p1.y() > p2.y() || (p1.y() == p2.y() && p1.x() > p2.x() ); } bool operator<( const QPoint& p1, const QPoint& p2) { return p1.y() < p2.y() || ( p1.y() == p2.y() && p1.x() < p2.x() ); } const QSet& Utilities::supportedVideoExtensions() { static QSet videoExtensions; if ( videoExtensions.empty() ) { videoExtensions.insert( QString::fromLatin1( "3gp" ) ); videoExtensions.insert( QString::fromLatin1( "avi" ) ); videoExtensions.insert( QString::fromLatin1( "mp4" ) ); videoExtensions.insert( QString::fromLatin1( "m4v" ) ); videoExtensions.insert( QString::fromLatin1( "mpeg" ) ); videoExtensions.insert( QString::fromLatin1( "mpg" ) ); videoExtensions.insert( QString::fromLatin1( "qt" ) ); videoExtensions.insert( QString::fromLatin1( "mov" ) ); videoExtensions.insert( QString::fromLatin1( "moov" ) ); videoExtensions.insert( QString::fromLatin1( "qtvr" ) ); videoExtensions.insert( QString::fromLatin1( "rv" ) ); videoExtensions.insert( QString::fromLatin1( "3g2" ) ); videoExtensions.insert( QString::fromLatin1( "fli" ) ); videoExtensions.insert( QString::fromLatin1( "flc" ) ); videoExtensions.insert( QString::fromLatin1( "mkv" ) ); videoExtensions.insert( QString::fromLatin1( "mng" ) ); videoExtensions.insert( QString::fromLatin1( "asf" ) ); videoExtensions.insert( QString::fromLatin1( "asx" ) ); videoExtensions.insert( QString::fromLatin1( "wmp" ) ); videoExtensions.insert( QString::fromLatin1( "wmv" ) ); videoExtensions.insert( QString::fromLatin1( "ogm" ) ); videoExtensions.insert( QString::fromLatin1( "rm" ) ); videoExtensions.insert( QString::fromLatin1( "flv" ) ); videoExtensions.insert( QString::fromLatin1( "webm" ) ); videoExtensions.insert( QString::fromLatin1( "mts" ) ); videoExtensions.insert( QString::fromLatin1( "ogg" ) ); videoExtensions.insert( QString::fromLatin1( "ogv" ) ); videoExtensions.insert( QString::fromLatin1( "m2ts" ) ); } return videoExtensions; } bool Utilities::isVideo( const DB::FileName& fileName ) { QFileInfo fi( fileName.relative() ); QString ext = fi.suffix().toLower(); return supportedVideoExtensions().contains( ext ); } bool Utilities::isRAW( const DB::FileName& fileName ) { return ImageManager::RAWImageDecoder::isRAW( fileName ); } QImage Utilities::scaleImage(const QImage &image, int w, int h, Qt::AspectRatioMode mode ) { return image.scaled( w, h, mode, Settings::SettingsData::instance()->smoothScale() ? Qt::SmoothTransformation : Qt::FastTransformation ); } QImage Utilities::scaleImage(const QImage &image, const QSize& s, Qt::AspectRatioMode mode ) { return scaleImage( image, s.width(), s.height(), mode ); } QString Utilities::cStringWithEncoding( const char *c_str, const QString& charset ) { QTextCodec* codec = QTextCodec::codecForName( charset.toLatin1() ); if (!codec) codec = QTextCodec::codecForLocale(); return codec->toUnicode( c_str ); } DB::MD5 Utilities::MD5Sum( const DB::FileName& fileName ) { DB::MD5 checksum; QFile file( fileName.absolute() ); if ( file.open( QIODevice::ReadOnly ) ) { QCryptographicHash md5calculator(QCryptographicHash::Md5); while ( !file.atEnd() ) { QByteArray md5Buffer( file.read( MD5_BUFFER_SIZE ) ); md5calculator.addData( md5Buffer ); } file.close(); checksum = DB::MD5(QString::fromLatin1(md5calculator.result().toHex())); } return checksum; } QColor Utilities::contrastColor( const QColor& col ) { if ( col.red() < 127 && col.green() < 127 && col.blue() < 127 ) return Qt::white; else return Qt::black; } void Utilities::saveImage( const DB::FileName& fileName, const QImage& image, const char* format ) { const QFileInfo info(fileName.absolute()); QDir().mkpath(info.path()); const bool ok = image.save(fileName.absolute(),format); Q_ASSERT(ok); Q_UNUSED(ok); } // vi:expandtab:tabstop=4 shiftwidth=4: diff --git a/Utilities/Util.h b/Utilities/Util.h index 7c5d9d04..f1f54967 100644 --- a/Utilities/Util.h +++ b/Utilities/Util.h @@ -1,83 +1,79 @@ /* Copyright (C) 2003-2010 Jesper K. Pedersen 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 2 of the License, or (at your option) 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef UTIL_H #define UTIL_H #include #include #include #include #include #include "Settings/SettingsData.h" #include "DB/ImageInfoList.h" #include #include "DB/MD5.h" namespace DB { class ImageInfo; } namespace Utilities { QString createInfoText( DB::ImageInfoPtr info, QMap >* ); QString formatAge(DB::CategoryPtr category,const QString& item, DB::ImageInfoPtr info); void checkForBackupFile( const QString& fileName, const QString& message = QString() ); bool ctrlKeyDown(); bool copy( const QString& from, const QString& to ); void copyList( const QStringList& from, const QString& directoryTo ); bool makeSymbolicLink( const QString& from, const QString& to ); bool makeHardLink( const QString& from, const QString& to ); void deleteDemo(); QString setupDemo(); bool canReadImage( const DB::FileName& fileName ); const QSet& supportedVideoExtensions(); bool isVideo( const DB::FileName& fileName ); bool isRAW( const DB::FileName& fileName ); QString locateDataFile(const QString& fileName); QString readFile( const QString& fileName ); -bool loadJPEG(QImage *img, const DB::FileName& imageFile, QSize* fullSize, int dim=-1); -bool loadJPEG(QImage *img, const DB::FileName& imageFile, QSize* fullSize, int dim=-1, char *membuf=NULL, size_t membufSize=0); -bool loadJPEG(QImage *img, const QByteArray& data, QSize* fullSize, int dim=-1); -bool isJPEG( const DB::FileName& fileName ); QString stripEndingForwardSlash( const QString& fileName ); QString absoluteImageFileName( const QString& relativeName ); QString imageFileNameToAbsolute( const QString& fileName ); QString relativeFolderName( const QString& fileName); QImage scaleImage(const QImage &image, int w, int h, Qt::AspectRatioMode mode=Qt::IgnoreAspectRatio ); QImage scaleImage(const QImage &image, const QSize& s, Qt::AspectRatioMode mode=Qt::IgnoreAspectRatio ); QString cStringWithEncoding( const char *c_str, const QString& charset ); DB::MD5 MD5Sum( const DB::FileName& fileName ); QColor contrastColor( const QColor& ); void saveImage( const DB::FileName& fileName, const QImage& image, const char* format ); } bool operator>( const QPoint&, const QPoint& ); bool operator<( const QPoint&, const QPoint& ); #endif /* UTIL_H */ // vi:expandtab:tabstop=4 shiftwidth=4: