diff --git a/CMakeLists.txt b/CMakeLists.txt index 561de253..dde379f0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,279 +1,269 @@ # FindKF5 requires CMake >= 2.8.12 cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR) project(Tellico) set(TELLICO_VERSION "3.2.1+git") set(QT_MIN_VERSION "5.4.0") # FindPoppler was added to ECM in 5.19 find_package(ECM 5.19 REQUIRED NO_MODULE) CMAKE_POLICY(SET CMP0028 OLD) if(POLICY CMP0063) cmake_policy(SET CMP0063 NEW) endif(POLICY CMP0063) # http://www.cmake.org/Wiki/CMake_Useful_Variables # automatically add CMAKE_CURRENT_SOURCE_DIR and CMAKE_CURRENT_BINARY_DIR # to the include directories in every processed CMakeLists.txt set(CMAKE_INCLUDE_CURRENT_DIR TRUE) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules) #include(ECMAddTests) #include(ECMMarkNonGuiExecutable) #include(ECMInstallIcons) include(ECMUninstallTarget) include(FeatureSummary) include(KDEInstallDirs) #include(KDECompilerSettings) include(KDEFrameworkCompilerSettings) include(KDECMakeSettings) ############# Options ################# option(ENABLE_AMAZON "Enable Amazon.com searching" TRUE) option(ENABLE_IMDB "Enable IMDb searching" TRUE) option(ENABLE_CDTEXT "Enable cdtext" TRUE) option(ENABLE_WEBCAM "Enable support for webcams" FALSE) option(BUILD_TESTS "Build the tests" TRUE) option(BUILD_FETCHER_TESTS "Build tests which verify data sources" FALSE) include(CheckSymbolExists) check_symbol_exists(strlwr "string.h" HAVE_STRLWR) check_symbol_exists(strupr "string.h" HAVE_STRUPR) find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Core Widgets Xml DBus Test Network ) find_package(KF5 REQUIRED COMPONENTS Archive Codecs Config ConfigWidgets CoreAddons Crash DocTools GuiAddons IconThemes ItemModels I18n JobWidgets KIO Solid Wallet WidgetsAddons WindowSystem XmlGui ) find_package(KF5KHtml REQUIRED NO_MODULE) include(MacroBoolTo01) find_package(Gettext REQUIRED) find_package(LibXml2 REQUIRED) find_package(LibXslt REQUIRED) find_package(KF5FileMetaData) set_package_properties(KF5FileMetaData PROPERTIES DESCRIPTION "Support for reading file metadata" URL "http://www.kde.org" TYPE OPTIONAL) find_package(KF5NewStuff) set_package_properties(KF5NewStuff PROPERTIES DESCRIPTION "Support for fetching new templates and scripts" URL "http://www.kde.org" TYPE OPTIONAL) find_package(KF5Sane) set_package_properties(KF5Sane PROPERTIES DESCRIPTION "Support for adding scanned images to a collection" URL "http://www.kde.org" TYPE OPTIONAL) if(KF5Sane_FOUND) include_directories(${KF5Sane_INCLUDE_DIR}) endif(KF5Sane_FOUND) -find_package(QImageBlitz) -set_package_properties(QImageBlitz PROPERTIES - DESCRIPTION "Support for image gradients in the default template" - URL "http://api.kde.org/kdesupport-api/kdesupport-apidocs/qimageblitz/html/" - TYPE OPTIONAL) -if(QImageBlitz_FOUND) - include_directories(${QIMAGEBLITZ_INCLUDES}) -endif(QImageBlitz_FOUND) - #macro_optional_find_package(KdepimLibs 4.5) #macro_log_feature(KDEPIMLIBS_FOUND "kdepimlibs" "Support for using the address book and calendar for loans" "http://pim.kde.org" FALSE "4.5.0" "") #find_package(KdepimLibs 4.5) #set_package_properties(KdepimLibs PROPERTIES # DESCRIPTION "Support for using the address book and calendar for loans" # URL "http://pim.kde.org" # TYPE OPTIONAL) if(KDEPIMLIBS_FOUND) include_directories(${KDEPIMLIBS_INCLUDE_DIRS}) endif(KDEPIMLIBS_FOUND) # There is a port of libkcddb to use KF5 style linking and headers find_package(KF5Cddb) set_package_properties(KF5Cddb PROPERTIES DESCRIPTION "Support for CDDB searches" URL "https://cgit.kde.org/libkcddb.git" TYPE OPTIONAL) find_package(Taglib) set_package_properties(Taglib PROPERTIES DESCRIPTION "Support for reading multimedia files" URL "http://taglib.github.io" TYPE OPTIONAL) if(TAGLIB_FOUND) add_definitions(${TAGLIB_CFLAGS}) include_directories(${TAGLIB_INCLUDES}) endif(TAGLIB_FOUND) find_package(Yaz 2.0) set_package_properties(Yaz PROPERTIES DESCRIPTION "Support for searching z39.50 databases" URL "http://www.indexdata.dk/yaz/" TYPE OPTIONAL) if(Yaz_FOUND) include_directories(${Yaz_INCLUDE_DIRS}) endif(Yaz_FOUND) # FindPoppler is part of ECM >= 5.19 find_package(Poppler COMPONENTS Qt5) find_package(Exempi 2.0) set_package_properties(Exempi PROPERTIES DESCRIPTION "Support for reading PDF/XMP metadata" URL "http://libopenraw.freedesktop.org/wiki/Exempi/" TYPE OPTIONAL) if(Exempi_FOUND) include_directories(${Exempi_INCLUDE_DIRS}) endif(Exempi_FOUND) find_package(Btparse) set_package_properties(Btparse PROPERTIES DESCRIPTION "External support for parsing and processing BibTeX data files" URL "https://metacpan.org/release/Text-BibTeX" TYPE OPTIONAL) if(Btparse_FOUND) include_directories(${Btparse_INCLUDE_DIRS}) set(TELLICO_BTPARSE_LIBS ${Btparse_LIBRARIES}) else(Btparse_FOUND) set(TELLICO_BTPARSE_LIBS btparse-tellico) endif(Btparse_FOUND) find_package(CDIO) set_package_properties(CDIO PROPERTIES DESCRIPTION "Support for reading cdtext from audio CDs" URL "https://www.gnu.org/software/libcdio/" TYPE OPTIONAL) if(CDIO_FOUND) include_directories(${CDIO_INCLUDE_DIRS}) endif(CDIO_FOUND) find_package(Csv 3.0) set_package_properties(Csv PROPERTIES DESCRIPTION "External support for reading CSV files" URL "http://sourceforge.net/projects/libcsv/" TYPE OPTIONAL) if(Csv_FOUND) include_directories(${Csv_INCLUDE_DIRS}) set(TELLICO_CSV_LIBS ${Csv_LIBRARIES}) else(Csv_FOUND) set(TELLICO_CSV_LIBS csv-tellico) endif(Csv_FOUND) # webcam uses libv4l, which only works on Linux for now # Linux 2.6.38 removed the videodev.h header # libv4l 0.8.3 includes a compat header for videodev.h if(ENABLE_WEBCAM) if(NOT CMAKE_SYSTEM_NAME MATCHES "Linux") message("WARNING: Webcam support is not available on your platform") set( ENABLE_WEBCAM FALSE ) else(NOT CMAKE_SYSTEM_NAME MATCHES "Linux") pkg_check_modules(LIBV4L libv4l1>=0.6) #macro_log_feature(LIBV4L_FOUND "libv4l" "Support for barcode scanning with a webcam" "http://hansdegoede.livejournal.com/3636.html" FALSE "" "") set_package_properties(LIBV4L PROPERTIES DESCRIPTION "Support for barcode scanning with a webcam" URL "http://hansdegoede.livejournal.com/3636.html" TYPE OPTIONAL) if(LIBV4L_FOUND) if(LIBV4L_VERSION VERSION_LESS "0.8.3" AND CMAKE_SYSTEM_VERSION VERSION_GREATER "2.6.37") message("WARNING: libv4l 0.8.3 or later is required for Linux kernel 2.6.38 or later") set( ENABLE_WEBCAM FALSE ) else(LIBV4L_VERSION VERSION_LESS "0.8.3" AND CMAKE_SYSTEM_VERSION VERSION_GREATER "2.6.37") include_directories(${LIBV4L_INCLUDE_DIR}) endif(LIBV4L_VERSION VERSION_LESS "0.8.3" AND CMAKE_SYSTEM_VERSION VERSION_GREATER "2.6.37") else(LIBV4L_FOUND) set(ENABLE_WEBCAM FALSE) endif(LIBV4L_FOUND) endif(NOT CMAKE_SYSTEM_NAME MATCHES "Linux") endif(ENABLE_WEBCAM) if(CMAKE_COMPILER_IS_GNUCXX) # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wnon-virtual-dtor -Wno-long-long -Wextra -fno-check-new -Woverloaded-virtual") # remove -Wno-deprecated when porting to KF5 is more advanced set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wnon-virtual-dtor -Wno-long-long -fno-check-new -Woverloaded-virtual -Wno-deprecated -Wno-deprecated-declarations") endif(CMAKE_COMPILER_IS_GNUCXX) #add_definitions(${QT_DEFINITIONS} # ${KDE4_DEFINITIONS} # ${YAZ_CFLAGS}) add_definitions(-DQT_STL -DQT_NO_CAST_FROM_ASCII -DQT_NO_URL_CAST_FROM_STRING) remove_definitions(-DQT_NO_STL) include_directories(${LIBXML2_INCLUDE_DIR} ${LIBXSLT_INCLUDE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${Tellico_SOURCE_DIR}/src/config ${Tellico_SOURCE_DIR}/src/3rdparty) set(TELLICO_DATA_INSTALL_DIR ${KDE_INSTALL_DATADIR}/tellico) add_subdirectory(src) add_subdirectory(icons) add_subdirectory(xslt) add_subdirectory(doc) ########## Wrap tests results around the tests done within the source -macro_bool_to_01(QImageBlitz_FOUND HAVE_QIMAGEBLITZ) macro_bool_to_01(TAGLIB_FOUND HAVE_TAGLIB) macro_bool_to_01(Poppler_Qt5_FOUND HAVE_POPPLER) macro_bool_to_01(Exempi_FOUND HAVE_EXEMPI) macro_bool_to_01(Yaz_FOUND HAVE_YAZ) macro_bool_to_01(KF5Sane_FOUND HAVE_KSANE) macro_bool_to_01(Libkcddb_FOUND HAVE_KCDDB) macro_bool_to_01(KF5Cddb_FOUND HAVE_KF5KCDDB) macro_bool_to_01(KDEPIMLIBS_FOUND HAVE_KABC) macro_bool_to_01(KDEPIMLIBS_FOUND HAVE_KCAL) macro_bool_to_01(LIBV4L_FOUND HAVE_V4L) macro_bool_to_01(KF5NewStuff_FOUND ENABLE_KNEWSTUFF3) macro_bool_to_01(Btparse_FOUND HAVE_LIBBTPARSE) macro_bool_to_01(CDIO_FOUND HAVE_CDIO) macro_bool_to_01(Csv_FOUND HAVE_LIBCSV) macro_bool_to_01(KF5FileMetaData_FOUND HAVE_KFILEMETADATA) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h) ########### install files ############### install(PROGRAMS org.kde.tellico.desktop DESTINATION ${KDE_INSTALL_APPDIR}) install(FILES tellico.dtd tellico.tips DESTINATION ${TELLICO_DATA_INSTALL_DIR}) install(FILES tellico.xml DESTINATION ${KDE_INSTALL_MIMEDIR}) install(FILES org.kde.tellico.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR} ) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/TODO b/TODO index 1a9fe102..19b6109d 100644 --- a/TODO +++ b/TODO @@ -1,27 +1,25 @@ anidb.net Douban * Implement multiple ISBN search and continued searching (for more than 20 results) Add Zotero query When adding new default value to a field, offer to insert value in existing entries Make sure all usages of FileRef* deletes the pointer Use FileRef for template installation to support network install brickset.com api MusicBrainz api v2 Supercat script: url is now http://gapines.org/opac/extras/opensearch/1.1/-/mods3/title/bible Centralize all the optional fields inside CollectionManager so the names are ensured to stay consistent. Add UI for adding to collection outside of fetcher results. -ComicVine API: https://comicvine.gamespot.com/api/ Science Direct API v2 https://dev.elsevier.com Replace most of the toggle actions in the view settings with KDualAction Update doc for MobyGames and data source changes -Port away from QImageBlitz to use QGradient Update the video game XSLT data sources to somehow use the platform normalization diff --git a/cmake/modules/FindQImageBlitz.cmake b/cmake/modules/FindQImageBlitz.cmake deleted file mode 100644 index 75cd828f..00000000 --- a/cmake/modules/FindQImageBlitz.cmake +++ /dev/null @@ -1,53 +0,0 @@ -# - Try to find the qimageblitz lib -# Once done this will define -# -# QIMAGEBLITZ_FOUND - system has qimageblitz lib -# QIMAGEBLITZ_INCLUDES - the qimageblitz include directory -# QIMAGEBLITZ_LIBRARIES - The libraries needed to use qimageblitz - -# Copyright (c) 2006, Montel Laurent, -# Copyright (c) 2007, Allen Winter, -# Redistribution and use is allowed according to the terms of the BSD license. -# For details see the accompanying COPYING-CMAKE-SCRIPTS file. - -include(FindLibraryWithDebug) - -if (QIMAGEBLITZ_INCLUDES AND QIMAGEBLITZ_LIBRARIES) - set(QImageBlitz_FIND_QUIETLY TRUE) -endif (QIMAGEBLITZ_INCLUDES AND QIMAGEBLITZ_LIBRARIES) - -if (NOT WIN32) - # use pkg-config to get the directories and then use these values - # in the FIND_PATH() and FIND_LIBRARY() calls - find_package(PkgConfig) - pkg_check_modules(PC_QIMAGEBLITZ5 QUIET qimageblitz5) - if(NOT PC_QIMAGEBLITZ5_FOUND) - pkg_check_modules(PC_QIMAGEBLITZ QUIET qimageblitz>=5.0) - endif() -endif (NOT WIN32) - -find_path(QIMAGEBLITZ_INCLUDES - NAMES qimageblitz.h - PATH_SUFFIXES qimageblitz5 qimageblitz - HINTS - $ENV{QIMAGEBLITZDIR}/include - ${PC_QIMAGEBLITZ5_INCLUDEDIR} - ${PC_QIMAGEBLITZ_INCLUDEDIR} - ${INCLUDE_INSTALL_DIR} -) - -find_library_with_debug(QIMAGEBLITZ_LIBRARIES - WIN32_DEBUG_POSTFIX d - NAMES qimageblitz5 qimageblitz - HINTS - $ENV{QIMAGEBLITZDIR}/lib - ${PC_QIMAGEBLITZ5_LIBDIR} - ${PC_QIMAGEBLITZ_LIBDIR} - ${LIB_INSTALL_DIR} -) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(QImageBlitz DEFAULT_MSG - QIMAGEBLITZ_INCLUDES QIMAGEBLITZ_LIBRARIES) - -mark_as_advanced(QIMAGEBLITZ_INCLUDES QIMAGEBLITZ_LIBRARIES) diff --git a/config.h.cmake b/config.h.cmake index 2a3992f2..7d65b9d3 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -1,44 +1,42 @@ #define TELLICO_VERSION "${TELLICO_VERSION}" #cmakedefine HAVE_STRLWR 1 #cmakedefine HAVE_STRUPR 1 #cmakedefine ENABLE_KNEWSTUFF3 -#cmakedefine HAVE_QIMAGEBLITZ - #cmakedefine HAVE_TAGLIB #cmakedefine HAVE_POPPLER #cmakedefine HAVE_EXEMPI #cmakedefine HAVE_KABC #cmakedefine HAVE_KCAL #cmakedefine HAVE_KCDDB #cmakedefine HAVE_KF5KCDDB #cmakedefine ENABLE_AMAZON #cmakedefine ENABLE_IMDB #cmakedefine ENABLE_CDTEXT #cmakedefine ENABLE_WEBCAM #cmakedefine HAVE_YAZ #cmakedefine HAVE_KSANE #cmakedefine HAVE_V4L #cmakedefine HAVE_LIBBTPARSE #cmakedefine HAVE_CDIO #cmakedefine HAVE_LIBCSV #cmakedefine HAVE_KFILEMETADATA diff --git a/src/images/CMakeLists.txt b/src/images/CMakeLists.txt index c7a7f288..f9ff30a2 100644 --- a/src/images/CMakeLists.txt +++ b/src/images/CMakeLists.txt @@ -1,25 +1,21 @@ SET(images_STAT_SRCS image.cpp imagedirectory.cpp imagefactory.cpp imageinfo.cpp imagejob.cpp ) add_library(images STATIC ${images_STAT_SRCS}) TARGET_LINK_LIBRARIES(images core config utils KF5::KIOCore KF5::Archive KF5::GuiAddons Qt5::Gui ) -IF( QImageBlitz_FOUND ) - TARGET_LINK_LIBRARIES(images ${QIMAGEBLITZ_LIBRARIES}) -ENDIF( QImageBlitz_FOUND ) - ADD_DEPENDENCIES(images tellico_config) diff --git a/src/images/imagefactory.cpp b/src/images/imagefactory.cpp index 22aebd72..019f2eea 100644 --- a/src/images/imagefactory.cpp +++ b/src/images/imagefactory.cpp @@ -1,754 +1,738 @@ /*************************************************************************** Copyright (C) 2003-2009 Robby Stephenson ***************************************************************************/ /*************************************************************************** * * * 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) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * 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 - #include "imagefactory.h" #include "image.h" #include "imageinfo.h" #include "imagedirectory.h" #include "imagejob.h" #include "../config/tellico_config.h" #include "../utils/tellico_utils.h" +#include "../utils/gradient.h" #include "../tellico_debug.h" #include #include #include #include -#ifdef HAVE_QIMAGEBLITZ -#include -#endif #define RELEASE_IMAGES using Tellico::ImageFactory; // this image info map is primarily for big images that don't fit // in the cache, so that don't have to be continually reloaded to get info QHash ImageFactory::s_imageInfoMap; Tellico::StringSet ImageFactory::s_imagesToRelease; Tellico::ImageFactory* ImageFactory::factory = nullptr; class ImageFactory::Private { public: - // since most images get turned into pixmaps quickly, use 10 megs - // for images and 10 megs for pixmaps Private() {} QHash imageDict; QCache imageCache; QCache pixmapCache; ImageDirectory dataImageDir; // kept in $HOME/.local/share/tellico/data/ ImageDirectory localImageDir; // kept local to data file TemporaryImageDirectory tempImageDir; // kept in tmp directory ImageZipArchive imageZipArchive; StringSet nullImages; }; ImageFactory::ImageFactory() : QObject(), d(new Private()) { } ImageFactory::~ImageFactory() { delete d; } void ImageFactory::init() { if(factory) { return; } factory = new ImageFactory(); factory->d->imageCache.setMaxCost(Config::imageCacheSize()); factory->d->pixmapCache.setMaxCost(Config::imageCacheSize()); factory->d->dataImageDir.setPath(Tellico::saveLocation(QStringLiteral("data/"))); } Tellico::ImageFactory* ImageFactory::self() { Q_ASSERT(factory && "ImageFactory is not initialized!"); return factory; } QString ImageFactory::tempDir() { return factory->d->tempImageDir.path(); } QString ImageFactory::dataDir() { return factory->d->dataImageDir.path(); } QString ImageFactory::localDir() { const QString dir = factory->d->localImageDir.path(); return dir.isEmpty() ? dataDir() : dir; } QString ImageFactory::imageDir() { Q_ASSERT(factory); switch(cacheDir()) { case LocalDir: return localDir(); case DataDir: return dataDir(); case ZipArchive: return tempDir(); case TempDir: return tempDir(); } return tempDir(); } Tellico::ImageFactory::CacheDir ImageFactory::cacheDir() { switch(Config::imageLocation()) { case Config::ImagesInLocalDir: return LocalDir; case Config::ImagesInAppDir: return DataDir; case Config::ImagesInFile: return TempDir; } return TempDir; } QString ImageFactory::addImage(const QUrl& url_, bool quiet_, const QUrl& refer_, bool link_) { Q_ASSERT(factory && "ImageFactory is not initialized!"); return factory->addImageImpl(url_, quiet_, refer_, link_).id(); } const Tellico::Data::Image& ImageFactory::addImageImpl(const QUrl& url_, bool quiet_, const QUrl& refer_, bool link_) { if(url_.isEmpty() || !url_.isValid() || d->nullImages.contains(url_.url())) { return Data::Image::null; } ImageJob* job = new ImageJob(url_, QString(), quiet_); job->setLinkOnly(link_); job->setReferrer(refer_); if(!job->exec()) { // myDebug() << "ImageJob failed to exec:" << job->errorString(); // ERR_UNKNOWN is used when the returned image is truly null // rather than network error or some such if(job->error() == KIO::ERR_UNKNOWN) { d->nullImages.add(url_.url()); } return Data::Image::null; } const Data::Image& img = job->image(); Q_ASSERT(!img.isNull()); // hold the image in memory since it probably isn't written locally to disk yet if(!d->imageDict.contains(img.id())) { d->imageDict.insert(img.id(), new Data::Image(img)); s_imageInfoMap.insert(img.id(), Data::ImageInfo(img)); } return img; } QString ImageFactory::addImage(const QImage& image_, const QString& format_) { Q_ASSERT(factory && "ImageFactory is not initialized!"); return factory->addImageImpl(image_, format_).id(); } QString ImageFactory::addImage(const QPixmap& pix_, const QString& format_) { Q_ASSERT(factory && "ImageFactory is not initialized!"); return factory->addImageImpl(pix_.toImage(), format_).id(); } const Tellico::Data::Image& ImageFactory::addImageImpl(const QImage& image_, const QString& format_) { Data::Image* img = new Data::Image(image_, format_); if(hasImageInMemory(img->id())) { const Data::Image& img2 = imageById(img->id()); if(!img2.isNull()) { delete img; return img2; } } if(img->isNull()) { delete img; return Data::Image::null; } d->imageDict.insert(img->id(), img); s_imageInfoMap.insert(img->id(), Data::ImageInfo(*img)); return *img; } QString ImageFactory::addImage(const QByteArray& data_, const QString& format_, const QString& id_) { Q_ASSERT(factory && "ImageFactory is not initialized!"); return factory->addImageImpl(data_, format_, id_).id(); } const Tellico::Data::Image& ImageFactory::addImageImpl(const QByteArray& data_, const QString& format_, const QString& id_) { if(id_.isEmpty()) { return Data::Image::null; } // do not call imageById(), it causes infinite looping with Document::loadImage() Data::Image* img = d->imageCache.object(id_); if(img) { myLog() << "already exists in cache: " << id_; return *img; } img = d->imageDict.value(id_); if(img) { myLog() << "already exists in dict: " << id_; return *img; } img = new Data::Image(data_, format_, id_); if(img->isNull()) { myDebug() << "NULL IMAGE!!!!!"; delete img; return Data::Image::null; } // myLog() << "// << " bytes, format = " << format_ // << ", id = "<< img->id(); d->imageDict.insert(img->id(), img); s_imageInfoMap.insert(img->id(), Data::ImageInfo(*img)); return *img; } const Tellico::Data::Image& ImageFactory::addCachedImageImpl(const QString& id_, CacheDir dir_) { // myLog() << "dir =" << (dir_ == DataDir ? "DataDir" : "TmpDir" ) << "; id =" << id_; Data::Image* img = nullptr; switch(dir_) { case DataDir: img = d->dataImageDir.imageById(id_); break; case LocalDir: img = d->localImageDir.imageById(id_); break; case TempDir: img = d->tempImageDir.imageById(id_); break; case ZipArchive: img = d->imageZipArchive.imageById(id_); break; } if(!img) { myWarning() << "image not found:" << id_; return Data::Image::null; } s_imageInfoMap.insert(img->id(), Data::ImageInfo(*img)); // if byteCount() is greater than maxCost, then trying and failing to insert it would // mean the image gets deleted if(img->byteCount() > d->imageCache.maxCost()) { // can't hold it in the cache myWarning() << "Image cache is unable to hold the image, it's too big!"; myWarning() << "Image name is " << img->id(); myWarning() << "Image size is " << img->byteCount(); myWarning() << "Max cache size is " << d->imageCache.maxCost(); // add it back to the dict, but add the image to the list of // images to release later. Necessary to avoid a memory leak since new Image() // was called, we need to keep the pointer d->imageDict.insert(img->id(), img); s_imagesToRelease.add(img->id()); } else if(!d->imageCache.insert(img->id(), img, img->byteCount())) { // at this point, img has been deleted! myWarning() << "Unable to insert into image cache"; return Data::Image::null; } return *img; } bool ImageFactory::writeCachedImage(const QString& id_, CacheDir dir_, bool force_ /*=false*/) { if(id_.isEmpty()) { return false; } // myLog() << "dir =" << (dir_ == DataDir ? "DataDir" : "TmpDir" ) << "; id =" << id_; ImageDirectory* imgDir = dir_ == DataDir ? &factory->d->dataImageDir : (dir_ == TempDir ? &factory->d->tempImageDir : &factory->d->localImageDir); Q_ASSERT(imgDir); bool success = writeCachedImage(id_, imgDir, force_); if(success) { // remove from dict and add to cache // it might not be in dict though if(factory->d->imageDict.contains(id_)) { Data::Image* img = factory->d->imageDict.take(id_); Q_ASSERT(img); // imageCache.insert will delete the image by itself if the cost exceeds the cache size if(factory->d->imageCache.insert(img->id(), img, img->byteCount())) { s_imageInfoMap.remove(id_); } } } return success; } bool ImageFactory::writeCachedImage(const QString& id_, ImageDirectory* imgDir_, bool force_ /*=false*/) { if(id_.isEmpty() || !imgDir_) { return false; } // myLog() << "dir =" << imgDir_->path() << "; id =" << id_; const bool exists = imgDir_->hasImage(id_); // only write if it doesn't exist bool success = (!force_ && exists); if(!success) { const Data::Image& img = imageById(id_); if(!img.isNull()) { // myLog() << "writing image"; success = imgDir_->writeImage(img); } } return success; } const Tellico::Data::Image& ImageFactory::imageById(const QString& id_) { Q_ASSERT(factory && "ImageFactory is not initialized!"); if(id_.isEmpty() || !factory || factory->d->nullImages.contains(id_)) { return Data::Image::null; } // myLog() << "imageById" << id_; // can't think of a better place to regularly check for images to release // but don't release image that just got asked for s_imagesToRelease.remove(id_); factory->releaseImages(); // first check the cache, used for images that are in the data file, or are only temporary // then the dict, used for images downloaded, but not yet saved anywhere Data::Image* img = factory->d->imageCache.object(id_); if(img) { // myLog() << "found in cache"; return *img; } img = factory->d->imageDict.value(id_); if(img) { // myLog() << "found in dict"; return *img; } // if the image is link only, we need to load it // but can't call imageInfo() since that might recurse into imageById() // also, the image info cache might not have it so check if the // id is a valid absolute url // yeah, it's probably slow if((s_imageInfoMap.contains(id_) && s_imageInfoMap[id_].linkOnly) || !QUrl(id_).isRelative()) { QUrl u(id_); if(u.isValid()) { return factory->addImageImpl(u, true, QUrl(), true); } } // the document does a delayed loading of the images, sometimes // so an image could be in the tmp dir and not be in the cache // or it could be too big for the cache if(factory->d->tempImageDir.hasImage(id_)) { const Data::Image& img2 = factory->addCachedImageImpl(id_, TempDir); if(!img2.isNull()) { // myLog() << "found in tmp dir"; return img2; } } // try to do a delayed loading of the image if(factory->d->imageZipArchive.hasImage(id_)) { const Data::Image& img2 = factory->addCachedImageImpl(id_, ZipArchive); if(!img2.isNull()) { // myLog() << "found in zip archive"; // go ahead and write image to disk so we don't have to keep it in memory // calling pixmap() could be loading all the covers, and we don't want one // to get pushed out of the cache yet writeCachedImage(id_, TempDir); return img2; } } // This section uses the config image setting plus the fallback location // to provide confidence that the user's image can be found CacheDir configLoc = TempDir; CacheDir fallbackLoc = TempDir; ImageDirectory* configImgDir = nullptr; ImageDirectory* fallbackImgDir = nullptr; if(Config::imageLocation() == Config::ImagesInLocalDir) { configLoc = LocalDir; fallbackLoc = DataDir; configImgDir = &factory->d->localImageDir; fallbackImgDir = &factory->d->dataImageDir; } else if(Config::imageLocation() == Config::ImagesInAppDir) { configLoc = DataDir; fallbackLoc = LocalDir; configImgDir = &factory->d->dataImageDir; fallbackImgDir = &factory->d->localImageDir; } // check the configured location first if(configImgDir && configImgDir->hasImage(id_)) { const Data::Image& img2 = factory->addCachedImageImpl(id_, configLoc); if(!img2.isNull()) { // myLog() << "found image in configured location" << configImgDir->path(); return img2; } else { myDebug() << "tried to add" << id_ << "from" << configImgDir->path() << "but failed"; } } else if(fallbackImgDir && fallbackImgDir->hasImage(id_)) { const Data::Image& img2 = factory->addCachedImageImpl(id_, fallbackLoc); if(!img2.isNull()) { // myLog() << "found image in fallback location" << fallbackImgDir->path() << id_; // the img is in the other location factory->emitImageMismatch(); return img2; } else { myDebug() << "tried to add" << id_ << "from" << fallbackImgDir->path() << "but failed"; } } // at this point, there's a possibility that the user has changed settings so that the images // are currently in a local or data directory, but Config::imageLocation() doesn't match that location if(factory->d->dataImageDir.hasImage(id_)) { const Data::Image& img2 = factory->addCachedImageImpl(id_, DataDir); if(!img2.isNull()) { // myLog() << "found image in data dir location"; return img2; } } if(factory->d->localImageDir.hasImage(id_)) { const Data::Image& img2 = factory->addCachedImageImpl(id_, LocalDir); if(!img2.isNull()) { // myLog() << "found image in local dir location"; return img2; } } // now, it appears the image doesn't exist. The only remaining possibility // is that the file name has multiple periods and as a result of the fix for bug 348088, // the image is lurking in a local image directory without the multiple periods if(Config::imageLocation() == Config::ImagesInLocalDir && QDir(localDir()).dirName().contains(QLatin1Char('.'))) { QString realImageDir = localDir(); QDir d(realImageDir); // try to cd up and into the other old directory QString cdString = QLatin1String("../") + d.dirName().section(QLatin1Char('.'), 0, 0) + QLatin1String("_files/"); if(d.cd(cdString)) { factory->d->localImageDir.setPath(d.path() + QDir::separator()); if(factory->d->localImageDir.hasImage(id_)) { // myDebug() << "Reading image from old local directory" << (cdString+id_); const Data::Image& img2 = factory->addCachedImageImpl(id_, LocalDir); // Be sure to reset the image directory location!! factory->d->localImageDir.setPath(realImageDir); factory->d->localImageDir.writeImage(img2); return img2; } factory->d->localImageDir.setPath(realImageDir); } } myDebug() << "***ImageFactory::imageById() - not found:" << id_; return Data::Image::null; } bool ImageFactory::hasLocalImage(const QString& id_) { Q_ASSERT(factory && "ImageFactory is not initialized!"); if(id_.isEmpty() || !factory) { return false; } const QUrl u(id_); return factory->d->imageCache.contains(id_) || factory->d->imageDict.contains(id_) || factory->d->tempImageDir.hasImage(id_) || factory->d->imageZipArchive.hasImage(id_) || (u.isValid() && !u.isRelative() && u.isLocalFile()) || (Config::imageLocation() == Config::ImagesInLocalDir && factory->d->localImageDir.hasImage(id_)) || (Config::imageLocation() == Config::ImagesInAppDir && factory->d->dataImageDir.hasImage(id_)); } void ImageFactory::requestImageById(const QString& id_) { Q_ASSERT(factory && "ImageFactory is not initialized!"); if(hasLocalImage(id_)) { emit factory->imageAvailable(id_); return; } if(factory->d->nullImages.contains(id_)) { // don't emit anything return; } const QUrl u(id_); // what does it mean when the Id is an absolute Url and yet the image is not link only? // Probably a heritage image id before the bugs were fixed. const bool linkOnly = (s_imageInfoMap.contains(id_) && s_imageInfoMap[id_].linkOnly); if(linkOnly || !u.isRelative()) { if(u.isValid()) { factory->requestImageByUrlImpl(u, true /* quiet */, QUrl() /* referrer */, linkOnly); if(!linkOnly) { myDebug() << "Loading an image url that is not link only. The image id will get updated."; } } } } void ImageFactory::requestImageByUrlImpl(const QUrl& url_, bool quiet_, const QUrl& refer_, bool link_) { ImageJob* job = new ImageJob(url_, QString() /* id, use calculated one */, quiet_); job->setLinkOnly(link_); job->setReferrer(refer_); connect(job, &ImageJob::result, this, &ImageFactory::slotImageJobResult); } Tellico::Data::ImageInfo ImageFactory::imageInfo(const QString& id_) { if(s_imageInfoMap.contains(id_)) { return s_imageInfoMap[id_]; } const Data::Image& img = imageById(id_); if(img.isNull()) { return Data::ImageInfo(); } return Data::ImageInfo(img); } void ImageFactory::cacheImageInfo(const Tellico::Data::ImageInfo& info) { s_imageInfoMap.insert(info.id, info); } bool ImageFactory::hasImageInfo(const QString& id_) { return s_imageInfoMap.contains(id_); } bool ImageFactory::validImage(const QString& id_) { // don't try s_imageInfoMap[id_] cause it inserts an empty image info return s_imageInfoMap.contains(id_) || factory->hasImageInMemory(id_) || !imageById(id_).isNull(); } QPixmap ImageFactory::pixmap(const QString& id_, int width_, int height_) { if(id_.isEmpty()) { return QPixmap(); } const QString key = id_ + QLatin1Char('|') + QString::number(width_) + QLatin1Char('|') + QString::number(height_); QPixmap* pix = factory->d->pixmapCache.object(key); if(pix) { return *pix; } const Data::Image& img = imageById(id_); if(img.isNull()) { return QPixmap(); } if(width_ > 0 && height_ > 0) { pix = new QPixmap(img.convertToPixmap(width_, height_)); } else { pix = new QPixmap(img.convertToPixmap()); } QPixmap pix2(*pix); // retain a copy of pix in case it doesn't go into the cache // pixmap size is w x h x d, divided by 8 bits const int size = (pix->width()*pix->height()*pix->depth()/8); if(!factory->d->pixmapCache.insert(key, pix, pix->width()*pix->height()*pix->depth()/8)) { // at this point, pix might be deleted myWarning() << "can't save in cache: " << id_; myWarning() << "### Current pixmap size is " << size; myWarning() << "### Max pixmap cache size is " << factory->d->pixmapCache.maxCost(); return pix2; } return *pix; } void ImageFactory::clean(bool purgeTempDirectory_) { // the caches all auto-delete s_imagesToRelease.clear(); qDeleteAll(factory->d->imageDict); factory->d->imageDict.clear(); s_imageInfoMap.clear(); factory->d->imageCache.clear(); factory->d->pixmapCache.clear(); if(purgeTempDirectory_) { factory->d->tempImageDir.purge(); // just to make sure all the image locations clean themselves up // delete the factory (which deletes the storage objects) and // then recreate the factory, in case anything else needs it // be sure to save local image directory if it's not a temp dir! const QString localDirName = localDir(); delete factory; factory = nullptr; ImageFactory::init(); if(QDir(localDirName).exists()) { setLocalDirectory(QUrl::fromLocalFile(localDirName)); } } } void ImageFactory::createStyleImages(int collectionType_, const Tellico::StyleOptions& opt_) { const QColor& baseColor = opt_.baseColor.isValid() ? opt_.baseColor : Config::templateBaseColor(collectionType_); const QColor& highColor = opt_.highlightedBaseColor.isValid() ? opt_.highlightedBaseColor : Config::templateHighlightedBaseColor(collectionType_); const QString bgname(QStringLiteral("gradient_bg.png")); const QColor& bgc1 = KColorUtils::mix(baseColor, highColor, 0.3); -#ifdef HAVE_QIMAGEBLITZ - QImage bgImage = Blitz::gradient(QSize(400, 1), bgc1, baseColor, - Blitz::PipeCrossGradient); -#else - QImage bgImage(QSize(400, 1), QImage::Format_RGB32); - bgImage.fill(bgc1); -#endif + QImage bgImage = Tellico::gradient(QSize(400, 1), bgc1, baseColor, + Tellico::PipeCrossGradient); bgImage = bgImage.transformed(QTransform().rotate(90)); const QString hdrname(QStringLiteral("gradient_header.png")); -#ifdef HAVE_QIMAGEBLITZ const QColor& bgc2 = KColorUtils::mix(baseColor, highColor, 0.5); - QImage hdrImage = Blitz::unbalancedGradient(QSize(1, 10), highColor, bgc2, - Blitz::VerticalGradient, 100, -100); -#else - QImage hdrImage(QSize(1, 10), QImage::Format_RGB32); - hdrImage.fill(highColor); -#endif + QImage hdrImage = Tellico::unbalancedGradient(QSize(1, 10), highColor, bgc2, + Tellico::VerticalGradient, 100, -100); if(opt_.imgDir.isEmpty()) { // write the style images both to the tmp dir and the cache dir // doesn't really hurt and lets the user switch back and forth ImageFactory::removeImage(bgname, true /*delete */); factory->addImageImpl(Data::Image::byteArray(bgImage, "PNG"), QStringLiteral("PNG"), bgname); ImageFactory::writeCachedImage(bgname, cacheDir(), true /*force*/); ImageFactory::writeCachedImage(bgname, TempDir, true /*force*/); ImageFactory::removeImage(hdrname, true /*delete */); factory->addImageImpl(Data::Image::byteArray(hdrImage, "PNG"), QStringLiteral("PNG"), hdrname); ImageFactory::writeCachedImage(hdrname, cacheDir(), true /*force*/); ImageFactory::writeCachedImage(hdrname, TempDir, true /*force*/); } else { bgImage.save(opt_.imgDir + bgname, "PNG"); hdrImage.save(opt_.imgDir + hdrname, "PNG"); } } void ImageFactory::removeImage(const QString& id_, bool deleteImage_) { // be careful using this delete factory->d->imageDict.take(id_); factory->d->imageCache.remove(id_); if(deleteImage_) { // remove from everywhere factory->d->dataImageDir.removeImage(id_); factory->d->localImageDir.removeImage(id_); factory->d->tempImageDir.removeImage(id_); } } Tellico::StringSet ImageFactory::imagesNotInCache() { StringSet set; QHash::const_iterator end = factory->d->imageDict.constEnd(); for(QHash::const_iterator it = factory->d->imageDict.constBegin(); it != end; ++it) { if(!factory->d->imageCache.contains(it.key())) { set.add(it.key()); } } return set; } bool ImageFactory::hasImageInMemory(const QString& id_) const { return d->imageCache.contains(id_) || d->imageDict.contains(id_); } bool ImageFactory::hasNullImage(const QString& id_) const { return d->nullImages.contains(id_); } // the purpose here is to remove images from the dict if they're is on the disk somewhere, // either in tempDir() or in dataDir(). The use for this is for calling pixmap() on an // image too big to stay in the cache. Then it stays in the dict forever. void ImageFactory::releaseImages() { #ifdef RELEASE_IMAGES if(s_imagesToRelease.isEmpty()) { return; } foreach(const QString& id, s_imagesToRelease) { if(!d->imageDict.contains(id)) { continue; } if(d->dataImageDir.hasImage(id) || d->localImageDir.hasImage(id) || d->tempImageDir.hasImage(id)) { delete d->imageDict.take(id); } } s_imagesToRelease.clear(); #endif } void ImageFactory::emitImageMismatch() { emit imageLocationMismatch(); } QString ImageFactory::localDirectory(const QUrl& url_) { if(url_.isEmpty()) { return QString(); } if(!url_.isLocalFile()) { myWarning() << "Tellico can only save images to local disk"; myWarning() << "unable to save to " << url_; return QString(); } QString dir = url_.adjusted(QUrl::RemoveFilename).path(); // could have already been set once if(!dir.contains(QLatin1String("_files"))) { QFileInfo fi(url_.fileName()); dir += fi.completeBaseName() + QLatin1String("_files/"); } return dir; } void ImageFactory::setLocalDirectory(const QUrl& url_) { const QString localDirName = localDirectory(url_); if(!localDirName.isEmpty()) { factory->d->localImageDir.setPath(localDirName); } } void ImageFactory::setZipArchive(KZip* zip_) { if(!zip_) { return; } factory->d->imageZipArchive.setZip(zip_); } void ImageFactory::slotImageJobResult(KJob* job_) { ImageJob* imageJob = qobject_cast(job_); Q_ASSERT(imageJob); if(!imageJob) { myWarning() << "No image job"; return; } const Data::Image& img = imageJob->image(); if(img.isNull()) { myDebug() << "null image for" << imageJob->url(); d->nullImages.add(imageJob->url().url()); // don't emit anything return; } // hold the image in memory since it probably isn't written locally to disk yet if(!d->imageDict.contains(img.id())) { d->imageDict.insert(img.id(), new Data::Image(img)); s_imageInfoMap.insert(img.id(), Data::ImageInfo(img)); } emit factory->imageAvailable(img.id()); } #undef RELEASE_IMAGES diff --git a/src/utils/CMakeLists.txt b/src/utils/CMakeLists.txt index e5fa2b24..afe2bd20 100644 --- a/src/utils/CMakeLists.txt +++ b/src/utils/CMakeLists.txt @@ -1,31 +1,32 @@ ########### next target ############### SET(utils_STAT_SRCS bibtexhandler.cpp cursorsaver.cpp datafileregistry.cpp + gradient.cpp guiproxy.cpp iso5426converter.cpp iso6937converter.cpp isbnvalidator.cpp lccnvalidator.cpp string_utils.cpp tellico_utils.cpp upcvalidator.cpp wallet.cpp xmlhandler.cpp ) add_library(utils STATIC ${utils_STAT_SRCS}) TARGET_LINK_LIBRARIES(utils Qt5::Core Qt5::Widgets Qt5::Xml KF5::CoreAddons # for KRandom KF5::Wallet KF5::I18n KF5::WidgetsAddons KF5::Codecs KF5::KIOCore ) diff --git a/src/utils/gradient.cpp b/src/utils/gradient.cpp new file mode 100644 index 00000000..001a174c --- /dev/null +++ b/src/utils/gradient.cpp @@ -0,0 +1,601 @@ +/* + Copyright (C) 1998, 1999, 2001, 2002, 2004, 2005, 2007 + Daniel M. Duley + (C) 2004 Zack Rusin + (C) 2000 Josef Weidendorfer + (C) 1999 Geert Jansen + (C) 1998, 1999 Christian Tibirna + (C) 1998, 1999 Dirk Mueller + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* + Diagonal gradient code was inspired by BlackBox. BlackBox gradients are + (C) Brad Hughes, and Mike Cole . + */ + +#include "gradient.h" + +#include +#include + +#include + +QImage Tellico::gradient(const QSize &size, const QColor &ca, + const QColor &cb, Tellico::GradientType eff) +{ + QImage image(size, QImage::Format_RGB32); + if(!size.isValid()) + return(image); + + int rca, gca, bca, rcb, gcb, bcb; + int rDiff = (rcb = cb.red()) - (rca = ca.red()); + int gDiff = (gcb = cb.green()) - (gca = ca.green()); + int bDiff = (bcb = cb.blue()) - (bca = ca.blue()); + int x, y; + QRgb rgb; + + if(eff == VerticalGradient || eff == HorizontalGradient){ + int rl = rca << 16; + int gl = gca << 16; + int bl = bca << 16; + QRgb *p; + if(eff == VerticalGradient){ + int rcdelta = ((1<<16) / size.height()) * rDiff; + int gcdelta = ((1<<16) / size.height()) * gDiff; + int bcdelta = ((1<<16) / size.height()) * bDiff; + for(y=0; y < size.height(); ++y){ + rl += rcdelta; + gl += gcdelta; + bl += bcdelta; + rgb = qRgb( (rl>>16), (gl>>16), (bl>>16) ); + + p = (QRgb *)image.scanLine(y); + for(x = 0; x < size.width(); ++x) + *p++ = rgb; + } + } + else{ // must be HorizontalGradient + int rcdelta = ((1<<16) / size.width()) * rDiff; + int gcdelta = ((1<<16) / size.width()) * gDiff; + int bcdelta = ((1<<16) / size.width()) * bDiff; + p = (QRgb *)image.scanLine(0); + for(x = 0; x < size.width(); ++x){ + rl += rcdelta; + gl += gcdelta; + bl += bcdelta; + *p++ = qRgb((rl>>16), (gl>>16), (bl>>16)); + } + p = (QRgb *)image.scanLine(0); + for(y = 1; y < size.height(); ++y) + memcpy(image.scanLine(y), p, size.width()*sizeof(QRgb)); + } + } + else{ + float rfd, gfd, bfd; + float rd = rca, gd = gca, bd = bca; + + int w = size.width(), h = size.height(); + int dw = w*2, dh = h*2; + unsigned char *xtable = new unsigned char[w*3]; + unsigned char *ytable = new unsigned char[h*3]; + + if(eff == DiagonalGradient || eff == CrossDiagonalGradient){ + rfd = (float)rDiff/dw; + gfd = (float)gDiff/dw; + bfd = (float)bDiff/dw; + + int dir; + for(x=0; x < w; x++, rd+=rfd, gd+=gfd, bd+=bfd) { + dir = eff == DiagonalGradient? x : w - x - 1; + xtable[dir*3] = (unsigned char) rd; + xtable[dir*3+1] = (unsigned char) gd; + xtable[dir*3+2] = (unsigned char) bd; + } + rfd = (float)rDiff/dh; + gfd = (float)gDiff/dh; + bfd = (float)bDiff/dh; + rd = gd = bd = 0; + for(y = 0; y < h; y++, rd+=rfd, gd+=gfd, bd+=bfd){ + ytable[y*3] = (unsigned char) rd; + ytable[y*3+1] = (unsigned char) gd; + ytable[y*3+2] = (unsigned char) bd; + } + + for(y = 0; y < h; y++){ + QRgb *p = (QRgb *)image.scanLine(y); + for(x = 0; x < w; x++){ + *p++ = qRgb(xtable[x*3] + ytable[y*3], + xtable[x*3+1] + ytable[y*3+1], + xtable[x*3+2] + ytable[y*3+2]); + } + } + } + else{ + int rSign = rDiff>0? 1: -1; + int gSign = gDiff>0? 1: -1; + int bSign = bDiff>0? 1: -1; + + rfd = (float)rDiff/w; + gfd = (float)gDiff/w; + bfd = (float)bDiff/w; + + rd = (float)rDiff/2; + gd = (float)gDiff/2; + bd = (float)bDiff/2; + + for(x=0; x < w; x++, rd-=rfd, gd-=gfd, bd-=bfd){ + xtable[x*3] = (unsigned char) qAbs((int)rd); + xtable[x*3+1] = (unsigned char) qAbs((int)gd); + xtable[x*3+2] = (unsigned char) qAbs((int)bd); + } + + rfd = (float)rDiff/h; + gfd = (float)gDiff/h; + bfd = (float)bDiff/h; + + rd = (float)rDiff/2; + gd = (float)gDiff/2; + bd = (float)bDiff/2; + + for(y=0; y < h; y++, rd-=rfd, gd-=gfd, bd-=bfd){ + ytable[y*3] = (unsigned char) qAbs((int)rd); + ytable[y*3+1] = (unsigned char) qAbs((int)gd); + ytable[y*3+2] = (unsigned char) qAbs((int)bd); + } + + dw = (w+1)>>1; + dh = (h+1)>>1; + int x2; + QRgb *sl1, *sl2; + for(y = 0; y < dh; y++){ + sl1 = (QRgb *)image.scanLine(y); + sl2 = (QRgb *)image.scanLine(qMax(h-y-1, y)); + for(x = 0, x2 = w-1; x < dw; x++, x2--){ + switch(eff){ + case PyramidGradient: + rgb = qRgb(rcb-rSign*(xtable[x*3]+ytable[y*3]), + gcb-gSign*(xtable[x*3+1]+ytable[y*3+1]), + bcb-bSign*(xtable[x*3+2]+ytable[y*3+2])); + break; + case RectangleGradient: + rgb = qRgb(rcb - rSign * + qMax(xtable[x*3], ytable[y*3]) * 2, + gcb - gSign * + qMax(xtable[x*3+1], ytable[y*3+1]) * 2, + bcb - bSign * + qMax(xtable[x*3+2], ytable[y*3+2]) * 2); + break; + case PipeCrossGradient: + rgb = qRgb(rcb - rSign * + qMin(xtable[x*3], ytable[y*3]) * 2, + gcb - gSign * + qMin(xtable[x*3+1], ytable[y*3+1]) * 2, + bcb - bSign * + qMin(xtable[x*3+2], ytable[y*3+2]) * 2); + break; + case EllipticGradient: + default: + rgb = qRgb(rcb - rSign * + (int)std::sqrt((xtable[x*3]*xtable[x*3] + + ytable[y*3]*ytable[y*3])*2.0f), + gcb - gSign * + (int)std::sqrt((xtable[x*3+1]*xtable[x*3+1] + + ytable[y*3+1]*ytable[y*3+1])*2.0f), + bcb - bSign * + (int)std::sqrt((xtable[x*3+2]*xtable[x*3+2] + + ytable[y*3+2]*ytable[y*3+2])*2.0f)); + break; + } + sl1[x] = sl2[x] = rgb; + sl1[x2] = sl2[x2] = rgb; + } + } + } + delete [] xtable; + delete [] ytable; + } + return(image); +} + +QImage Tellico::grayGradient(const QSize &size, unsigned char ca, + unsigned char cb, Tellico::GradientType eff) +{ + QImage image(size, QImage::Format_Indexed8); + if(!size.isValid()) + return(image); + QVector colorTable(256); + for(int i=0; i < 256; ++i) + colorTable[i] = qRgba(i, i, i, 255); + image.setColorTable(colorTable); + + int diff = cb - ca; + int x, y; + unsigned char idx; + + if(eff == VerticalGradient || eff == HorizontalGradient){ + int val = ca << 16; + unsigned char *p; + if(eff == VerticalGradient){ + int delta = ((1<<16) / size.height()) * diff; + for(y=0; y < size.height(); ++y){ + val += delta; + idx = val >> 16; + p = image.scanLine(y); + for(x = 0; x < size.width(); ++x) + *p++ = idx; + } + } + else{ // must be HorizontalGradient + int delta = ((1<<16) / size.width()) * diff; + p = image.scanLine(0); + for(x = 0; x < size.width(); ++x){ + val += delta; + *p++ = val >> 16; + } + p = image.scanLine(0); + for(y = 1; y < size.height(); ++y) + memcpy(image.scanLine(y), p, image.bytesPerLine()); + } + } + else{ + float delta, val=ca; + + unsigned int w = size.width(), h = size.height(); + unsigned char *xtable = new unsigned char[w]; + unsigned char *ytable = new unsigned char[h]; + w*=2, h*=2; + + if(eff == DiagonalGradient || eff == CrossDiagonalGradient){ + delta = (float)diff/w; + int dir; + for(x=0; x < size.width(); x++, val+=delta){ + dir = eff == DiagonalGradient? x : size.width() - x - 1; + xtable[dir] = (unsigned char) val; + } + delta = (float)diff/h; + val = 0; + for(y = 0; y < size.height(); y++, val+=delta) + ytable[y] = (unsigned char) val; + + for(y = 0; y < size.height(); y++){ + unsigned char *p = image.scanLine(y); + for(x = 0; x < size.width(); x++) + *p++ = xtable[x] + ytable[y]; + } + } + else{ + int sign = diff>0? 1: -1; + delta = (float)diff / size.width(); + val = (float)diff/2; + for(x=0; x < size.width(); x++, val-=delta) + xtable[x] = (unsigned char) qAbs((int)val); + + delta = (float)diff/size.height(); + val = (float)diff/2; + for(y=0; y < size.height(); y++, val-=delta) + ytable[y] = (unsigned char) qAbs((int)val); + + int w = (size.width()+1)>>1; + int h = (size.height()+1)>>1; + int x2; + unsigned char *sl1, *sl2; + for(y = 0; y < h; y++){ + sl1 = image.scanLine(y); + sl2 = image.scanLine(qMax(size.height()-y-1, y)); + for(x = 0, x2 = size.width()-1; x < w; x++, x2--){ + switch(eff){ + case PyramidGradient: + idx = cb-sign*(xtable[x]+ytable[y]); + break; + case RectangleGradient: + idx = cb-sign*qMax(xtable[x], ytable[y])*2; + break; + case PipeCrossGradient: + idx = cb-sign*qMin(xtable[x], ytable[y])*2; + break; + case EllipticGradient: + default: + idx = cb - sign * + (int)std::sqrt((xtable[x]*xtable[x] + + ytable[y]*ytable[y])*2.0f); + break; + } + sl1[x] = sl2[x] = idx; + sl1[x2] = sl2[x2] = idx; + } + } + } + delete [] xtable; + delete [] ytable; + } + return(image); +} + +QImage Tellico::unbalancedGradient(const QSize &size, const QColor &ca, + const QColor &cb, Tellico::GradientType eff, + int xfactor, int yfactor) +{ + QImage image(size, QImage::Format_RGB32); + if(!size.isValid()) + return image; + + int dir; // general parameter used for direction switches + bool _xanti = (xfactor < 0); // negative on X direction + bool _yanti = (yfactor < 0); // negative on Y direction + xfactor = qBound(1, qAbs(xfactor), 200); + yfactor = qBound(1, qAbs(yfactor), 200); + // float xbal = xfactor/5000.; + // float ybal = yfactor/5000.; + float xbal = xfactor/30.0f/size.width(); + float ybal = yfactor/30.0f/size.height(); + float rat; + + int x, y; + int rca, gca, bca, rcb, gcb, bcb; + int rDiff = (rcb = cb.red()) - (rca = ca.red()); + int gDiff = (gcb = cb.green()) - (gca = ca.green()); + int bDiff = (bcb = cb.blue()) - (bca = ca.blue()); + + if(eff == VerticalGradient || eff == HorizontalGradient){ + QRgb *p; + if(eff == VerticalGradient){ + QRgb rgbRow; + for(y=0; y < size.height(); y++){ + dir = _yanti ? y : size.height() - 1 - y; + rat = 1 - std::exp( - (float)y * ybal ); + p = (QRgb *) image.scanLine(dir); + rgbRow = qRgb(rcb - (int) ( rDiff * rat ), + gcb - (int) ( gDiff * rat ), + bcb - (int) ( bDiff * rat )); + for(x = 0; x < size.width(); x++) + *p++ = rgbRow; + } + } + else{ + p = (QRgb *)image.scanLine(0); + for(x = 0; x < size.width(); x++){ + dir = _xanti ? x : size.width() - 1 - x; + rat = 1 - std::exp( - (float)x * xbal ); + p[dir] = qRgb(rcb - (int) ( rDiff * rat ), + gcb - (int) ( gDiff * rat ), + bcb - (int) ( bDiff * rat )); + } + + p = (QRgb *)image.scanLine(0); + for(y = 1; y < size.height(); ++y){ + memcpy(image.scanLine(y), p, + size.width()*sizeof(QRgb)); + } + } + } + else{ + int w=size.width(), h=size.height(); + unsigned char *xtable = new unsigned char[w*3]; + unsigned char *ytable = new unsigned char[h*3]; + QRgb *p; + + if(eff == DiagonalGradient || eff == CrossDiagonalGradient){ + for(x = 0; x < w; x++){ + dir = _xanti ? x : w - 1 - x; + rat = 1 - std::exp( - (float)x * xbal ); + + xtable[dir*3] = (unsigned char) ( rDiff/2 * rat ); + xtable[dir*3+1] = (unsigned char) ( gDiff/2 * rat ); + xtable[dir*3+2] = (unsigned char) ( bDiff/2 * rat ); + } + + for(y = 0; y < h; y++){ + dir = _yanti ? y : h - 1 - y; + rat = 1 - std::exp( - (float)y * ybal ); + + ytable[dir*3] = (unsigned char) ( rDiff/2 * rat ); + ytable[dir*3+1] = (unsigned char) ( gDiff/2 * rat ); + ytable[dir*3+2] = (unsigned char) ( bDiff/2 * rat ); + } + + for(y = 0; y < h; y++){ + p = (QRgb *)image.scanLine(y); + for(x = 0; x < w; x++){ + *p++ = qRgb(rcb - (xtable[x*3] + ytable[y*3]), + gcb - (xtable[x*3+1] + ytable[y*3+1]), + bcb - (xtable[x*3+2] + ytable[y*3+2])); + } + } + } + else{ + int rSign = rDiff>0? 1: -1; + int gSign = gDiff>0? 1: -1; + int bSign = bDiff>0? 1: -1; + + for(x = 0; x < w; x++){ + dir = _xanti ? x : w - 1 - x; + rat = 1 - std::exp( - (float)x * xbal ); + + xtable[dir*3] = (unsigned char) qAbs((int)(rDiff*(0.5-rat))); + xtable[dir*3+1] = (unsigned char) qAbs((int)(gDiff*(0.5-rat))); + xtable[dir*3+2] = (unsigned char) qAbs((int)(bDiff*(0.5-rat))); + } + + for(y = 0; y < h; y++){ + dir = _yanti ? y : h - 1 - y; + rat = 1 - std::exp( - (float)y * ybal ); + + ytable[dir*3] = (unsigned char) qAbs((int)(rDiff*(0.5-rat))); + ytable[dir*3+1] = (unsigned char) qAbs((int)(gDiff*(0.5-rat))); + ytable[dir*3+2] = (unsigned char) qAbs((int)(bDiff*(0.5-rat))); + } + + for(y = 0; y < h; y++){ + p = (QRgb *)image.scanLine(y); + for(x = 0; x < w; x++) { + if (eff == PyramidGradient){ + *p++ = qRgb(rcb-rSign*(xtable[x*3]+ytable[y*3]), + gcb-gSign*(xtable[x*3+1]+ytable[y*3+1]), + bcb-bSign*(xtable[x*3+2]+ytable[y*3+2])); + } + else if (eff == RectangleGradient){ + *p++ = qRgb(rcb - rSign * + qMax(xtable[x*3], ytable[y*3]) * 2, + gcb - gSign * + qMax(xtable[x*3+1], ytable[y*3+1]) * 2, + bcb - bSign * + qMax(xtable[x*3+2], ytable[y*3+2]) * 2); + } + else if (eff == PipeCrossGradient){ + *p++ = qRgb(rcb - rSign * + qMin(xtable[x*3], ytable[y*3]) * 2, + gcb - gSign * + qMin(xtable[x*3+1], ytable[y*3+1]) * 2, + bcb - bSign * + qMin(xtable[x*3+2], ytable[y*3+2]) * 2); + } + else if (eff == EllipticGradient){ + *p++ = qRgb(rcb - rSign * + (int)std::sqrt((xtable[x*3]*xtable[x*3] + + ytable[y*3]*ytable[y*3])*2.0), + gcb - gSign * + (int)std::sqrt((xtable[x*3+1]*xtable[x*3+1] + + ytable[y*3+1]*ytable[y*3+1])*2.0), + bcb - bSign * + (int)std::sqrt((xtable[x*3+2]*xtable[x*3+2] + + ytable[y*3+2]*ytable[y*3+2])*2.0)); + } + } + } + } + delete [] xtable; + delete [] ytable; + } + return(image); +} + +QImage Tellico::grayUnbalancedGradient(const QSize &size, unsigned char ca, + unsigned char cb, Tellico::GradientType eff, + int xfactor, int yfactor) +{ + QImage image(size, QImage::Format_Indexed8); + if(!size.isValid()) + return(image); + QVector colorTable(256); + for(int i=0; i < 256; ++i) + colorTable[i] = qRgba(i, i, i, 255); + image.setColorTable(colorTable); + + int dir; // general parameter used for direction switches + bool _xanti = (xfactor < 0); // negative on X direction + bool _yanti = (yfactor < 0); // negative on Y direction + xfactor = qBound(1, qAbs(xfactor), 200); + yfactor = qBound(1, qAbs(yfactor), 200); + float xbal = xfactor/30.0f/size.width(); + float ybal = yfactor/30.0f/size.height(); + float rat; + + int x, y; + int diff = cb-ca; + + if(eff == VerticalGradient || eff == HorizontalGradient){ + unsigned char *p; + if(eff == VerticalGradient){ + unsigned char idx; + for(y=0; y < size.height(); y++){ + dir = _yanti ? y : size.height() - 1 - y; + rat = 1 - std::exp( - (float)y * ybal ); + p = image.scanLine(dir); + idx = cb - (int)( diff * rat ); + for(x = 0; x < size.width(); x++) + *p++ = idx; + } + } + else{ + p = image.scanLine(0); + for(x = 0; x < size.width(); x++){ + dir = _xanti ? x : size.width() - 1 - x; + rat = 1 - std::exp( - (float)x * xbal ); + p[dir] = cb - (int)( diff * rat ); + } + + p = image.scanLine(0); + for(y = 1; y < size.height(); ++y) + memcpy(image.scanLine(y), p, image.bytesPerLine()); + } + } + else{ + int w=size.width(), h=size.height(); + unsigned char *xtable = new unsigned char[w]; + unsigned char *ytable = new unsigned char[h]; + unsigned char *p; + + if(eff == DiagonalGradient || eff == CrossDiagonalGradient){ + for(x = 0; x < w; x++){ + dir = _xanti ? x : w - 1 - x; + rat = 1 - std::exp( - (float)x * xbal ); + xtable[dir] = (unsigned char) ( diff/2 * rat ); + } + + for(y = 0; y < h; y++){ + dir = _yanti ? y : h - 1 - y; + rat = 1 - std::exp( - (float)y * ybal ); + ytable[dir] = (unsigned char) ( diff/2 * rat ); + } + + for(y = 0; y < h; y++){ + p = image.scanLine(y); + for(x = 0; x < w; x++) + *p++ = cb - (xtable[x] + ytable[y]); + } + } + else{ + int sign = diff>0? 1: -1; + for(x = 0; x < w; x++){ + dir = _xanti ? x : w - 1 - x; + rat = 1 - std::exp( - (float)x * xbal ); + xtable[dir] = (unsigned char) qAbs((int)(diff*(0.5-rat))); + } + + for(y = 0; y < h; y++){ + dir = _yanti ? y : h - 1 - y; + rat = 1 - std::exp( - (float)y * ybal ); + ytable[dir] = (unsigned char) qAbs((int)(diff*(0.5-rat))); + } + + for(y = 0; y < h; y++){ + p = image.scanLine(y); + for(x = 0; x < w; x++) { + if (eff == PyramidGradient) + *p++ = cb-sign*(xtable[x]+ytable[y]); + else if (eff == RectangleGradient) + *p++ = cb -sign*qMax(xtable[x], ytable[y])*2; + else if (eff == PipeCrossGradient) + *p++ = cb-sign*qMin(xtable[x], ytable[y])*2; + else if (eff == EllipticGradient) + *p++ = cb-sign * (int)std::sqrt((xtable[x]*xtable[x] + + ytable[y]*ytable[y])*2.0); + } + } + } + delete [] xtable; + delete [] ytable; + } + return(image); +} diff --git a/src/utils/gradient.h b/src/utils/gradient.h new file mode 100644 index 00000000..9b0a5bd6 --- /dev/null +++ b/src/utils/gradient.h @@ -0,0 +1,100 @@ +#ifndef __BLITZ_H +#define __BLITZ_H + +/* + Copyright (C) 1998, 1999, 2001, 2002, 2004, 2005, 2007 + Daniel M. Duley + (C) 2004 Zack Rusin + (C) 2000 Josef Weidendorfer + (C) 1999 Geert Jansen + (C) 1998, 1999 Christian Tibirna + (C) 1998, 1999 Dirk Mueller + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include + +namespace Tellico { + + enum GradientType {VerticalGradient=0, HorizontalGradient, DiagonalGradient, + CrossDiagonalGradient, PyramidGradient, RectangleGradient, + PipeCrossGradient, EllipticGradient}; + + /** + * Create a gradient from color a to color b of the specified type. + * + * @param size The desired size of the gradient. + * @param ca Color a + * @param cb Color b + * @param type The type of gradient. + * @return The gradient. + */ + QImage gradient(const QSize &size, const QColor &ca, + const QColor &cb, GradientType type); + /** + * Creates an 8bit grayscale gradient suitable for use as an alpha channel + * using QImage::setAlphaChannel(). + * + * @param size The desired size of the gradient. + * @param ca The grayscale start value. + * @param cb The grayscale end value. + * @param type The type of gradient. + * @return The gradient. + */ + QImage grayGradient(const QSize &size, unsigned char ca, + unsigned char cb, GradientType type); + /** + * Create an unbalanced gradient. An unbalanced gradient is a gradient + * where the transition from color a to color b is not linear, but in this + * case exponential. + * + * @param size The desired size of the gradient. + * @param ca Color a + * @param cb Color b + * @param type The type of gradient. + * @param xfactor The x decay length. Use a value between -200 and 200. + * @param yfactor The y decay length. + * @return The gradient. + */ + QImage unbalancedGradient(const QSize &size, const QColor &ca, + const QColor &cb, GradientType type, + int xfactor=100, int yfactor=100); + /** + * Creates an 8bit grayscale gradient suitable for use as an alpha channel + * using QImage::setAlphaChannel(). + * + * @param size The desired size of the gradient. + * @param type The type of gradient. + * @param ca The grayscale start value. + * @param cb The grayscale end value. + * @param xfactor The x decay length. Use a value between -200 and 200. + * @param yfactor The y decay length. + * @return The gradient. + */ + QImage grayUnbalancedGradient(const QSize &size, unsigned char ca, + unsigned char cb, GradientType type, + int xfactor=100, int yfactor=100); +} + +#endif