diff --git a/libs/album/album.h b/libs/album/album.h index 614bde693d..94b0966b75 100644 --- a/libs/album/album.h +++ b/libs/album/album.h @@ -1,580 +1,580 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2004-06-15 * Description : digiKam album types * * Copyright (C) 2004-2005 by Renchi Raju * Copyright (C) 2006-2017 by Gilles Caulier * Copyright (C) 2014-2015 by Mohamed Anwer * * 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, 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. * * ============================================================ */ #ifndef ALBUM_H #define ALBUM_H // Qt includes #include #include #include #include #include #include // Local includes #include "coredbalbuminfo.h" #include "digikam_export.h" namespace Digikam { /** Album list type definition. */ class Album; typedef QList AlbumList; class CoreDbUrl; /** * \class Album * \brief Abstract base class for all album types * * A class which provides an abstraction for a type Album. This class is meant to * be derived and every time a new Album Type is defined add a enum corresponding * to that to Album::Type * * This class provides a means of building a tree representation for * Albums @see Album::setParent(). */ class DIGIKAM_EXPORT Album { public: enum Type { PHYSICAL=0, /**< PHYSICAL: A physical album type @see PAlbum */ TAG, /**< TAG: A tag album type @see TAlbum */ DATE, /**< DATE: A date album type @see DAlbum */ SEARCH, /**< SEARCH: A search album type @see SAlbum */ FACE /**< FACE: A faces album type @see FAlbum */ }; /** * @return the parent album for this album */ Album* parent() const; /** * @return the first child of this album or 0 if no children */ Album* firstChild() const; /** * @return the last child of this album or 0 if no children */ Album* lastChild() const; /** * @return the next sibling of this album of this album or 0 * if no next sibling * @see AlbumIterator */ Album* next() const; /** * @return the previous sibling of this album of this album or 0 if no * previous sibling * @see AlbumIterator */ Album* prev() const; /** * @return a list of all child Albums */ AlbumList childAlbums(bool recursive = false); /** * @return a list of all child Albums */ QList childAlbumIds(bool recursive = false); /** * @return the type of album * @see Type */ Type type() const; /** * Each album has a @p ID uniquely identifying it in the set of Albums of * a Type * * \note The @p ID for a root Album is always 0 * * @return the @p ID of the album * @see globalID() */ int id() const; /** * An album ID is only unique among the set of all Albums of its Type. * This is a global Identifier which will uniquely identifying the Album * among all Albums * * \note If you are adding a new Album Type make sure to update * this implementation. * * You can always get the @p ID of the album using something like * * \code * int albumID = rootAlbum->globalID() - album->globalID(); * \endcode * * @return the @p globalID of the album * @see id() */ int globalID() const; /** * @return the @p title aka name of the album */ QString title() const; /** * @return the kde url of the album */ virtual CoreDbUrl databaseUrl() const = 0; /** * @return true is the album is a Root Album */ bool isRoot() const; /** * @return true if the @p album is in the parent hierarchy * * @param album Album to check whether it belongs in the child * hierarchy */ bool isAncestorOf(Album* const album) const; /** * @return true if the Album was created by Labels Tree * */ bool isUsedByLabelsTree() const; /** * @return true if the album was created to be a trash * virtual album */ bool isTrashAlbum() const; /** * This allows to associate some "extra" data to a Album. As one * Album can be used by several objects (often views) which all need * to add some data, you have to use a key to reference your extra data * within the Album. * * That way a Album can hold and provide access to all those views * separately. * * for eg, * * \code * album->setExtraData( this, searchFolderItem ); * \endcode * * and can later access the searchFolderItem by doing * * \code * SearchFolderItem *item = static_cast(album->extraData(this)); * \endcode * * Note: you have to remove and destroy the data you associated yourself * when you don't need it anymore! * * @param key the key of the extra data * @param value the value of the extra data * @see extraData * @see removeExtraData */ void setExtraData(const void* const key, void* const value); /** * Remove the associated extra data associated with @p key * * @param key the key of the extra data * @see setExtraData * @see extraData */ void removeExtraData(const void* const key); /** * Retrieve the associated extra data associated with @p key * * @param key the key of the extra data * @see setExtraData * @see extraData */ void* extraData(const void* const key) const; /** * Sets the property m_usedByLabelsTree to true if the search album * was created using the Colors and labels tree view * * @param isUsed => the status of the usage */ void setUsedByLabelsTree(bool isUsed); /** * @brief Produces the global id * @param type The type of the album * @param id the (type-specific) id of the album * @return the global id */ static int globalID(Type type, int id); protected: /** * Constructor */ Album(Album::Type type, int id, bool root); /** * Destructor * * this will also recursively delete all child Albums */ virtual ~Album(); /** * Delete all child albums and also remove any associated extra data */ void clear(); /** * @internal use only * * Set a new title for the album * * @param title new title for the album */ void setTitle(const QString& title); /** * @internal use only * * Set the parent of the album * * @param parent set the parent album of album to @p parent */ void setParent(Album* const parent); /** * @internal use only * * Insert an Album as a child for this album * * @param child the Album to add as child */ void insertChild(Album* const child); /** * @internal use only * * Remove a Album from the children list for this album * * @param child the Album to remove */ void removeChild(Album* const child); private: /** * Disable copy and default constructor */ Album(); Album(const Album&); Album& operator==(const Album&); private: bool m_root; bool m_clearing; bool m_usedByLabelsTree; int m_id; QString m_name; QString m_title; QMap m_extraMap; Type m_type; Album* m_parent; Album* m_firstChild; Album* m_lastChild; Album* m_next; Album* m_prev; friend class AlbumManager; }; /** * \class PAlbum * * A Physical Album representation */ class DIGIKAM_EXPORT PAlbum : public Album { public: /// Constructor for root album explicit PAlbum(const QString& title); /// Constructor for album root albums PAlbum(int albumRoot, const QString& label); /// Constructor for normal albums PAlbum(int albumRoot, const QString& parentPath, const QString& title, int id); /// Constructor for Trash album PAlbum(const QString& parentPath, int albumRoot); ~PAlbum(); void setCaption(const QString& caption); void setCategory(const QString& category); void setDate(const QDate& date); QString albumRootPath() const; QString albumRootLabel() const; int albumRootId() const; QString caption() const; QString category() const; QDate date() const; QString albumPath() const; QString prettyUrl() const; QString folderPath() const; CoreDbUrl databaseUrl() const; QUrl fileUrl() const; qlonglong iconId() const; bool isAlbumRoot() const; private: /// A special integer for Trash virtual folders Ids; /// That gets decremented not incremented static int m_uniqueTrashId; bool m_isAlbumRootAlbum; int m_albumRootId; QString m_path; QString m_parentPath; QString m_category; QString m_caption; qlonglong m_iconId; QDate m_date; friend class AlbumManager; }; /** * \class TAlbum * * A Tag Album representation */ class DIGIKAM_EXPORT TAlbum : public Album { public: TAlbum(const QString& title, int id, bool root=false); ~TAlbum(); /** * @return The tag path, e.g. "/People/Friend/John" if leadingSlash is true, "People/Friend/John" if leadingSlash if false. * The root TAlbum returns "/" resp. "". */ QString tagPath(bool leadingSlash = true) const; CoreDbUrl databaseUrl() const; QString prettyUrl() const; QString icon() const; qlonglong iconId() const; QList tagIDs() const; bool isInternalTag() const; bool hasProperty(const QString& key) const; QString property(const QString& key) const; QMap properties() const; private: int m_pid; QString m_icon; qlonglong m_iconId; friend class AlbumManager; }; /** * \class DAlbum * * A Date Album representation */ class DIGIKAM_EXPORT DAlbum : public Album { public: enum Range { Month = 0, Year }; public: explicit DAlbum(const QDate& date, bool root=false, Range range=Month); ~DAlbum(); QDate date() const; Range range() const; CoreDbUrl databaseUrl() const; private: static int m_uniqueID; QDate m_date; Range m_range; friend class AlbumManager; }; /** * \class SAlbum * * A Search Album representation */ class DIGIKAM_EXPORT SAlbum : public Album { public: SAlbum(const QString& title, int id, bool root=false); ~SAlbum(); CoreDbUrl databaseUrl() const; QString query() const; DatabaseSearch::Type searchType() const; bool isNormalSearch() const; bool isAdvancedSearch() const; bool isKeywordSearch() const; bool isTimelineSearch() const; bool isHaarSearch() const; bool isMapSearch() const; bool isDuplicatesSearch() const; /** * Indicates whether this album is a temporary search or not. * * @return true if this is a temporary search album, else false */ bool isTemporarySearch() const; QString displayTitle() const; /** - * Returns the title of search albums that is used to to mark them as a + * Returns the title of search albums that is used to mark them as a * temporary search that isn't saved officially yet and is only used for * viewing purposes. * * @param type type of the search to get the temporary title for * @param haarType there are several haar searches, so that this search type * needs a special handling * @return string that identifies this album uniquely as an unsaved search */ static QString getTemporaryTitle(DatabaseSearch::Type type, DatabaseSearch::HaarSearchType haarType = DatabaseSearch::HaarImageSearch); /** * Returns the title for a temporary haar search depending on the sub-type * used for this search * * @param haarType type of the haar search to get the name for * @return string that identifies this album uniquely as an unsaved search */ static QString getTemporaryHaarTitle(DatabaseSearch::HaarSearchType haarType); private: void setSearch(DatabaseSearch::Type type, const QString& query); private: QString m_query; DatabaseSearch::Type m_searchType; friend class AlbumManager; }; /** * \class AlbumIterator * * Iterate over all children of this Album. * \note It will not include the specified album * * Example usage: * \code * AlbumIterator it(album); * while ( it.current() ) * { * qCDebug(DIGIKAM_GENERAL_LOG) << "Album: " << it.current()->title(); * ++it; * } * \endcode * * \warning Do not delete albums using this iterator. */ class DIGIKAM_EXPORT AlbumIterator { public: explicit AlbumIterator(Album* const album); ~AlbumIterator(); AlbumIterator& operator++(); Album* operator*(); Album* current() const; private: // disable copying and construction without an album AlbumIterator(); Q_DISABLE_COPY(AlbumIterator) private: Album* m_current; Album* m_root; }; } // namespace Digikam Q_DECLARE_METATYPE(Digikam::Album*) #endif // ALBUM_H diff --git a/libs/database/imgqsort/imgqsort.cpp b/libs/database/imgqsort/imgqsort.cpp index c0cb82beec..89edaba37a 100644 --- a/libs/database/imgqsort/imgqsort.cpp +++ b/libs/database/imgqsort/imgqsort.cpp @@ -1,846 +1,846 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 25/08/2013 * Description : Image Quality Sorter * * Copyright (C) 2013-2017 by Gilles Caulier * Copyright (C) 2013-2014 by Gowtham Ashok * * 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, 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. * * ============================================================ */ #include "libopencv.h" #include "imgqsort.h" // C++ includes #include #include #include // Qt includes #include #include // Local includes #include "digikam_debug.h" #include "mixerfilter.h" #include "nrfilter.h" #include "nrestimate.h" // To switch on/off log trace file. // #define TRACE 1 using namespace cv; namespace Digikam { class ImgQSort::Private { public: Private() : clusterCount(30), //used for k-means clustering algorithm in noise detection size(512) { for (int c = 0 ; c < 3; c++) { fimg[c] = 0; } // Setting the default values edgeThresh = 1; lowThreshold = 0.4; ratio = 3; kernel_size = 3; blurrejected = 0.0; blur = 0.0; acceptedThreshold = 0.0; pendingThreshold = 0.0; rejectedThreshold = 0.0; label = 0; running = true; } float* fimg[3]; const uint clusterCount; const uint size; // Size of squared original image. Mat src; // Matrix of the original source image Mat src_gray; // Matrix of the grayscaled source image Mat detected_edges; // Matrix containing only edges in the image int edgeThresh; // threshold above which we say that edges are present at a point int ratio; // lower:upper threshold for canny edge detector algorithm int kernel_size; - // kernel size for for the Sobel operations to be performed internally + // kernel size for the Sobel operations to be performed internally // by the edge detector double lowThreshold; DImg image; // original image DImg neimage; // noise estimation image[ for color] ImageQualitySettings imq; double blurrejected; double blur; double acceptedThreshold; double pendingThreshold; double rejectedThreshold; QString path; // Path to host result file PickLabel* label; volatile bool running; }; ImgQSort::ImgQSort(const DImg& img, const ImageQualitySettings& imq, PickLabel* const label) : d(new Private) { // Reading settings from GUI d->imq.detectBlur = imq.detectBlur; d->imq.detectNoise = imq.detectNoise; d->imq.detectCompression = imq.detectCompression; d->imq.detectOverexposure = imq.detectOverexposure; d->imq.lowQRejected = imq.lowQRejected; d->imq.mediumQPending = imq.mediumQPending; d->imq.highQAccepted = imq.highQAccepted; d->imq.speed = imq.speed; d->imq.rejectedThreshold = imq.rejectedThreshold; d->imq.pendingThreshold = imq.pendingThreshold; d->imq.acceptedThreshold = imq.acceptedThreshold; d->imq.blurWeight = imq.blurWeight; d->imq.noiseWeight = imq.noiseWeight; d->imq.compressionWeight = imq.compressionWeight; d->image = img; d->neimage = img; d->label = label; } ImgQSort::~ImgQSort() { delete d; } void ImgQSort::startAnalyse() { // For Noise Estimation // Use the Top/Left corner of 256x256 pixels to analyse noise contents from image. // This will speed-up computation time with OpenCV. readImage(); double blur = 0.0; short blur2 = 0; double noise = 0.0; int compressionlevel = 0; float finalquality = 0.0; int exposurelevel = 0; // If blur option is selected in settings, run the blur detection algorithms if (d->running && d->imq.detectBlur) { // Returns blur value between 0 and 1. // If NaN is returned just assign NoPickLabel blur = blurdetector(); qCDebug(DIGIKAM_DATABASE_LOG) << "Amount of Blur present in image is : " << blur; // Returns blur value between 1 and 32767. // If 1 is returned just assign NoPickLabel blur2 = blurdetector2(); qCDebug(DIGIKAM_DATABASE_LOG) << "Amount of Blur present in image [using LoG Filter] is : " << blur2; } if (d->running && d->imq.detectNoise) { // Some images give very low noise value. Assign NoPickLabel in that case. // Returns noise value between 0 and 1. noise = noisedetector(); qCDebug(DIGIKAM_DATABASE_LOG) << "Amount of Noise present in image is : " << noise; } if (d->running && d->imq.detectCompression) { // Returns number of blocks in the image. compressionlevel = compressiondetector(); qCDebug(DIGIKAM_DATABASE_LOG) << "Amount of compression artifacts present in image is : " << compressionlevel; } if (d->running && d->imq.detectOverexposure) { // Returns if there is overexposure in the image exposurelevel = exposureamount(); qCDebug(DIGIKAM_DATABASE_LOG) << "Exposure level present in image is : " << exposurelevel; } #ifdef TRACE QFile filems("imgqsortresult.txt"); if (filems.open(QIODevice::Append | QIODevice::Text)) { QTextStream oms(&filems); oms << "File:" << d->image.originalFilePath() << endl; if (d->imq.detectBlur) { oms << "Blur Present:" << blur << endl; oms << "Blur Present(using LoG filter):"<< blur2 << endl; } if (d->imq.detectNoise) { oms << "Noise Present:" << noise << endl; } if (d->imq.detectCompression) { oms << "Compression Present:" << compressionlevel << endl; } if (d->imq.detectOverexposure) { oms << "Exposure Present:" << exposurelevel << endl; } } #endif // TRACE // Calculating finalquality // All the results to have a range of 1 to 100. if (d->running) { float finalblur = (blur*100) + ((blur2/32767)*100); float finalnoise = noise*100; float finalcompression = (compressionlevel / 1024) * 100; // we are processing 1024 pixels size image finalquality = finalblur*d->imq.blurWeight + finalnoise*d->imq.noiseWeight + finalcompression*d->imq.compressionWeight; finalquality = finalquality / 100; // Assigning PickLabels if (finalquality == 0.0) { // Algorithms have not been run. So return noPickLabel *d->label = NoPickLabel; } else if (finalquality < d->imq.rejectedThreshold) { *d->label = RejectedLabel; } else if (finalquality > d->imq.rejectedThreshold && finalquality < d->imq.acceptedThreshold) { *d->label = PendingLabel; } else { *d->label = AcceptedLabel; } } else { *d->label = NoPickLabel; } } void ImgQSort::cancelAnalyse() { d->running = false; } void ImgQSort::readImage() const { MixerContainer settings; settings.bMonochrome = true; MixerFilter mixer(&d->image, 0L, settings); mixer.startFilterDirectly(); d->image.putImageData(mixer.getTargetImage().bits()); #if OPENCV_TEST_VERSION(3,0,0) d->src = cvCreateMat(d->image.numPixels(), 3, CV_8UC3); // Create a matrix containing the pixel values of original image d->src_gray = cvCreateMat(d->image.numPixels(), 1, CV_8UC1); // Create a matrix containing the pixel values of grayscaled image #else d->src = Mat(d->image.numPixels(), 3, CV_8UC3); // Create a matrix containing the pixel values of original image d->src_gray = Mat(d->image.numPixels(), 1, CV_8UC1); // Create a matrix containing the pixel values of grayscaled image #endif if (d->imq.detectNoise) { DColor col; for (int c = 0; d->running && (c < 3); c++) { d->fimg[c] = new float[d->neimage.numPixels()]; } int j = 0; for (uint y = 0; d->running && (y < d->neimage.height()); y++) { for (uint x = 0; d->running && (x < d->neimage.width()); x++) { col = d->neimage.getPixelColor(x, y); d->fimg[0][j] = col.red(); d->fimg[1][j] = col.green(); d->fimg[2][j] = col.blue(); j++; } } } } void ImgQSort::CannyThreshold(int, void*) const { // Reduce noise with a kernel 3x3. blur(d->src_gray, d->detected_edges, Size(3,3)); // Canny detector. Canny(d->detected_edges, d->detected_edges, d->lowThreshold, d->lowThreshold*d->ratio,d-> kernel_size); } double ImgQSort::blurdetector() const { d->lowThreshold = 0.4; d->ratio = 3; double maxval = 0.0; ImgQSort::CannyThreshold(0, 0); double average = mean(d->detected_edges)[0]; int* const maxIdx = new int[sizeof(d->detected_edges)]; minMaxIdx(d->detected_edges, 0, &maxval, 0, maxIdx); double blurresult = average / maxval; qCDebug(DIGIKAM_DATABASE_LOG) << "The average of the edge intensity is " << average; qCDebug(DIGIKAM_DATABASE_LOG) << "The maximum of the edge intensity is " << maxval; qCDebug(DIGIKAM_DATABASE_LOG) << "The result of the edge intensity is " << blurresult; delete [] maxIdx; return blurresult; } short ImgQSort::blurdetector2() const { // Algorithm using Laplacian of Gaussian Filter to detect blur. Mat out; Mat noise_free; qCDebug(DIGIKAM_DATABASE_LOG) << "Algorithm using LoG Filter started"; // To remove noise from the image GaussianBlur(d->src_gray, noise_free, Size(3,3), 0, 0, BORDER_DEFAULT); // Aperture size of 1 corresponds to the correct matrix int kernel_size = 3; int scale = 1; int delta = 0; int ddepth = CV_16S; Laplacian(noise_free, out, ddepth, kernel_size, scale, delta, BORDER_DEFAULT); // noise_free: The input image without noise. // out: Destination (output) image // ddepth: Depth of the destination image. Since our input is CV_8U we define ddepth = CV_16S to avoid overflow // kernel_size: The kernel size of the Sobel operator to be applied internally. We use 3 ihere short maxLap = -32767; for (int i = 0; i < out.rows; i++) { for (int j = 0; j < out.cols; j++) { short value = out.at(i,j); if (value > maxLap) { maxLap = value ; } } } return maxLap; } double ImgQSort::noisedetector() const { double noiseresult = 0.0; //--convert fimg to CvMat*------------------------------------------------------------------------------- // Convert the image into YCrCb color model. NRFilter::srgb2ycbcr(d->fimg, d->neimage.numPixels()); // One dimentional CvMat which stores the image. CvMat* points = cvCreateMat(d->neimage.numPixels(), 3, CV_32FC1); // Matrix to store the index of the clusters. CvMat* clusters = cvCreateMat(d->neimage.numPixels(), 1, CV_32SC1); // Pointer variable to handle the CvMat* points (the image in CvMat format). float* pointsPtr = reinterpret_cast(points->data.ptr); for (uint x=0 ; d->running && (x < d->neimage.numPixels()) ; x++) { for (int y=0 ; d->running && (y < 3) ; y++) { *pointsPtr++ = (float)d->fimg[y][x]; } } // Array to store the centers of the clusters. CvArr* centers = 0; qCDebug(DIGIKAM_DATABASE_LOG) << "Everything ready for the cvKmeans2 or as it seems to"; //-- KMEANS --------------------------------------------------------------------------------------------- if (d->running) { cvKMeans2(points, d->clusterCount, clusters, cvTermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 10, 1.0), 3, 0, 0, centers, 0); } qCDebug(DIGIKAM_DATABASE_LOG) << "cvKmeans2 successfully run"; //-- Divide into cluster->columns, sample->rows, in matrix standard deviation --------------------------- QScopedArrayPointer rowPosition(new int[d->clusterCount]); // The row position array would just make the hold the number of elements in each cluster. for (uint i=0 ; d->running && (i < d->clusterCount) ; i++) { // Initializing the cluster count array. rowPosition[i] = 0; } int rowIndex, columnIndex; for (uint i=0 ; d->running && (i < d->neimage.numPixels()) ; i++) { columnIndex = clusters->data.i[i]; rowPosition[columnIndex]++; } qCDebug(DIGIKAM_DATABASE_LOG) << "array indexed, and ready to find maximum"; //-- Finding maximum of the rowPosition array ------------------------------------------------------------ int max = rowPosition[0]; for (uint i=1 ; d->running && (i < d->clusterCount) ; i++) { if (rowPosition[i] > max) { max = rowPosition[i]; } } QString maxString; maxString.append(QString::number(max)); qCDebug(DIGIKAM_DATABASE_LOG) << QString::fromUtf8("maximum declared = %1").arg(maxString); //-- Divide and conquer --------------------------------------------------------------------------------- CvMat* sd = 0; if (d->running) { sd = cvCreateMat(max, (d->clusterCount * points->cols), CV_32FC1); } //-- Initialize the rowPosition array ------------------------------------------------------------------- QScopedArrayPointer rPosition(new int[d->clusterCount]); for (uint i=0 ; d->running && (i < d->clusterCount) ; i++) { rPosition[i] = 0; } float* ptr = 0; qCDebug(DIGIKAM_DATABASE_LOG) << "The rowPosition array is ready!"; for (uint i=0 ; d->running && (i < d->neimage.numPixels()) ; i++) { columnIndex = clusters->data.i[i]; rowIndex = rPosition[columnIndex]; // Moving to the right row. ptr = reinterpret_cast(sd->data.ptr + rowIndex*(sd->step)); // Moving to the right column. for (int j=0 ; d->running && (j < columnIndex) ; j++) { for (int z=0 ; d->running && (z < (points->cols)) ; z++) { ptr++; } } for (int z=0 ; d->running && (z < (points->cols)) ; z++) { *ptr++ = cvGet2D(points, i, z).val[0]; } rPosition[columnIndex] = rPosition[columnIndex] + 1; } qCDebug(DIGIKAM_DATABASE_LOG) << "sd matrix creation over!"; //-- This part of the code would involve the sd matrix and make the mean and the std of the data ------------- CvScalar std; CvScalar mean; CvMat* meanStore = 0; CvMat* stdStore = 0; float* meanStorePtr = 0; float* stdStorePtr = 0; int totalcount = 0; // Number of non-empty clusters. if (d->running) { meanStore = cvCreateMat(d->clusterCount, points->cols, CV_32FC1); stdStore = cvCreateMat(d->clusterCount, points->cols, CV_32FC1); meanStorePtr = reinterpret_cast(meanStore->data.ptr); stdStorePtr = reinterpret_cast(stdStore->data.ptr); } for (int i=0 ; d->running && (i < sd->cols) ; i++) { if (d->running && (rowPosition[(i/points->cols)] >= 1)) { CvMat* workingArr = cvCreateMat(rowPosition[(i / points->cols)], 1, CV_32FC1); ptr = reinterpret_cast(workingArr->data.ptr); for (int j=0 ; d->running && (j < rowPosition[(i / (points->cols))]) ; j++) { *ptr++ = cvGet2D(sd, j, i).val[0]; } cvAvgSdv(workingArr, &mean, &std); *meanStorePtr++ = (float)mean.val[0]; *stdStorePtr++ = (float)std.val[0]; totalcount++; cvReleaseMat(&workingArr); } } qCDebug(DIGIKAM_DATABASE_LOG) << "Make the mean and the std of the data"; // ----------------------------------------------------------------------------------------------------------------- if (d->running) { meanStorePtr = reinterpret_cast(meanStore->data.ptr); stdStorePtr = reinterpret_cast(stdStore->data.ptr); } qCDebug(DIGIKAM_DATABASE_LOG) << "Done with the basic work of storing the mean and the std"; //-- Calculating weighted mean, and weighted std ----------------------------------------------------------- QString info; float weightedMean = 0.0F; float weightedStd = 0.0F; float datasd[3] = {0.0F, 0.0F, 0.0F}; for (int j=0 ; d->running && (j < points->cols) ; j++) { meanStorePtr = reinterpret_cast(meanStore->data.ptr); stdStorePtr = reinterpret_cast(stdStore->data.ptr); for (int moveToChannel=0 ; moveToChannel <= j ; moveToChannel++) { meanStorePtr++; stdStorePtr++; } for (uint i=0 ; i < d->clusterCount ; i++) { if (rowPosition[i] >= 1) { weightedMean += (*meanStorePtr) * rowPosition[i]; weightedStd += (*stdStorePtr) * rowPosition[i]; meanStorePtr += points->cols; stdStorePtr += points->cols; } } weightedMean = weightedMean / (d->neimage.numPixels()); weightedStd = weightedStd / (d->neimage.numPixels()); datasd[j] = weightedStd; info.append(QLatin1String("\n\nChannel: ")); info.append(QString::number(j)); info.append(QLatin1String("\nWeighted Mean: ")); info.append(QString::number(weightedMean)); info.append(QLatin1String("\nWeighted Standard Deviation: ")); info.append(QString::number(weightedStd)); } qCDebug(DIGIKAM_DATABASE_LOG) << "Info : " << info; // -- adaptation --------------------------------------------------------------------------------------- if (d->running) { // For 16 bits images only. if (d->neimage.sixteenBit()) { for (int i=0 ; i < points->cols ; i++) { datasd[i] = datasd[i] / 256; } } noiseresult = ((datasd[0]/2)+(datasd[1]/2)+(datasd[2]/2))/3; qCDebug(DIGIKAM_DATABASE_LOG) << "All is completed"; //-- releasing matrices and closing files ---------------------------------------------------------------------- cvReleaseMat(&sd); cvReleaseMat(&stdStore); cvReleaseMat(&meanStore); cvReleaseMat(&points); cvReleaseMat(&clusters); for (uint i = 0; i < 3; i++) { delete [] d->fimg[i]; } } return noiseresult; } int ImgQSort::compressiondetector() const { //FIXME: set threshold value to an acceptable standard to get the number of blocking artifacts const int THRESHOLD = 30; const int block_size = 8; int countblocks = 0; int number_of_blocks = 0; int sum = 0; #if OPENCV_TEST_VERSION(3,0,0) vector average_bottom, average_middle, average_top; #else std::vector average_bottom, average_middle, average_top; #endif // Go through 8 blocks at a time horizontally // iterating through columns. for (int i = 0; d->running && i < d->src_gray.rows; i++) { // Calculating intensity of top column. for (int j = 0; j < d->src_gray.cols; j+=8) { sum = 0; for (int k = j; k < block_size; k++) { sum += (int)d->src_gray.at(i, j); } average_top.push_back(sum/8); } // Calculating intensity of middle column. for (int j = 0; j < d->src_gray.cols; j+=8) { sum = 0; for (int k = j; k < block_size; k++) { sum += (int)d->src_gray.at(i+1, j); } average_middle.push_back(sum/8); } // Calculating intensity of bottom column. countblocks = 0; for (int j = 0; j < d->src_gray.cols; j+=8) { sum = 0; for (int k = j; k < block_size; k++) { sum += (int)d->src_gray.at(i+2, j); } average_bottom.push_back(sum/8); countblocks++; } // Check if the average intensity of 8 blocks in the top, middle and bottom rows are equal. // If so increment number_of_blocks. for (int j = 0; j < countblocks; j++) { if ((average_middle[j] == (average_top[j]+average_bottom[j])/2) && average_middle[j] > THRESHOLD) { number_of_blocks++; } } } average_bottom.clear(); average_middle.clear(); average_top.clear(); // Iterating through rows. for (int j = 0; d->running && j < d->src_gray.cols; j++) { // Calculating intensity of top row. for (int i = 0; i < d->src_gray.rows; i+=8) { sum = 0; for (int k = i; k < block_size; k++) { sum += (int)d->src_gray.at(i, j); } average_top.push_back(sum/8); } // Calculating intensity of middle row. for (int i= 0; i < d->src_gray.rows; i+=8) { sum = 0; for (int k = i; k < block_size; k++) { sum += (int)d->src_gray.at(i, j+1); } average_middle.push_back(sum/8); } // Calculating intensity of bottom row. countblocks = 0; for (int i = 0; i < d->src_gray.rows; i+=8) { sum = 0; for (int k = i; k < block_size; k++) { sum += (int)d->src_gray.at(i, j+2); } average_bottom.push_back(sum/8); countblocks++; } // Check if the average intensity of 8 blocks in the top, middle and bottom rows are equal. // If so increment number_of_blocks. for (int i = 0; i < countblocks; i++) { if ((average_middle[i] == (average_top[i]+average_bottom[i])/2) && average_middle[i] > THRESHOLD) { number_of_blocks++; } } } return number_of_blocks; } int ImgQSort::exposureamount() const { /// Separate the image in 3 places ( B, G and R ) #if OPENCV_TEST_VERSION(3,0,0) vector bgr_planes; #else std::vector bgr_planes; #endif split(d->src, bgr_planes); /// Establish the number of bins int histSize = 256; /// Set the ranges ( for B,G,R) ) float range[] = { 0, 256 } ; const float* histRange = { range }; bool uniform = true; bool accumulate = false; Mat b_hist, g_hist, r_hist; /// Compute the histograms calcHist(&bgr_planes[0], 1, 0, Mat(), b_hist, 1, &histSize, &histRange, uniform, accumulate); calcHist(&bgr_planes[1], 1, 0, Mat(), g_hist, 1, &histSize, &histRange, uniform, accumulate); calcHist(&bgr_planes[2], 1, 0, Mat(), r_hist, 1, &histSize, &histRange, uniform, accumulate); // Draw the histograms for B, G and R int hist_w = 512; int hist_h = 400; Mat histImage( hist_h, hist_w, CV_8UC3, Scalar( 0,0,0) ); /// Normalize the histograms: normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat()); normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat()); normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat()); /// Sum the histograms Scalar rmean,gmean,bmean; rmean = mean(r_hist); gmean = mean(g_hist); bmean = mean(b_hist); int exposurelevel = (rmean[0] + gmean[0] + bmean[0]) / 3; return exposurelevel; } } // namespace Digikam diff --git a/libs/dngwriter/extra/dng_sdk/dng_negative.cpp b/libs/dngwriter/extra/dng_sdk/dng_negative.cpp index 79815f4bc5..8bb7c4c572 100644 --- a/libs/dngwriter/extra/dng_sdk/dng_negative.cpp +++ b/libs/dngwriter/extra/dng_sdk/dng_negative.cpp @@ -1,3183 +1,3183 @@ /*****************************************************************************/ // Copyright 2006-2008 Adobe Systems Incorporated // All Rights Reserved. // // NOTICE: Adobe permits you to use, modify, and distribute this file in // accordance with the terms of the Adobe license agreement accompanying it. /*****************************************************************************/ /* $Id: //mondo/dng_sdk_1_3/dng_sdk/source/dng_negative.cpp#1 $ */ /* $DateTime: 2009/06/22 05:04:49 $ */ /* $Change: 578634 $ */ /* $Author: tknoll $ */ /*****************************************************************************/ #include "dng_negative.h" #include "dng_abort_sniffer.h" #include "dng_bottlenecks.h" #include "dng_camera_profile.h" #include "dng_color_spec.h" #include "dng_exceptions.h" #include "dng_globals.h" #include "dng_host.h" #include "dng_image.h" #include "dng_image_writer.h" #include "dng_info.h" #include "dng_linearization_info.h" #include "dng_memory_stream.h" #include "dng_mosaic_info.h" #include "dng_preview.h" #include "dng_simple_image.h" #include "dng_tag_codes.h" #include "dng_tag_values.h" #include "dng_tile_iterator.h" #include "dng_xmp.h" /*****************************************************************************/ dng_noise_profile::dng_noise_profile () : fNoiseFunctions () { } /*****************************************************************************/ dng_noise_profile::dng_noise_profile (const std::vector &functions) : fNoiseFunctions (functions) { } /*****************************************************************************/ bool dng_noise_profile::IsValid () const { if (NumFunctions () == 0 || NumFunctions () > kMaxColorPlanes) { return false; } for (uint32 plane = 0; plane < NumFunctions (); plane++) { if (!NoiseFunction (plane).IsValid ()) { return false; } } return true; } /*****************************************************************************/ bool dng_noise_profile::IsValidForNegative (const dng_negative &negative) const { if (!(NumFunctions () == 1 || NumFunctions () == negative.ColorChannels ())) { return false; } return IsValid (); } /*****************************************************************************/ const dng_noise_function & dng_noise_profile::NoiseFunction (uint32 plane) const { if (NumFunctions () == 1) { return fNoiseFunctions.front (); } DNG_REQUIRE (plane < NumFunctions (), "Bad plane index argument for NoiseFunction ()."); return fNoiseFunctions [plane]; } /*****************************************************************************/ uint32 dng_noise_profile::NumFunctions () const { return (uint32) fNoiseFunctions.size (); } /*****************************************************************************/ dng_negative::dng_negative (dng_memory_allocator &allocator) : fAllocator (allocator) , fModelName () , fLocalName () , fHasBaseOrientation (false) , fBaseOrientation () , fDefaultCropSizeH () , fDefaultCropSizeV () , fDefaultCropOriginH (0, 1) , fDefaultCropOriginV (0, 1) , fDefaultScaleH (1, 1) , fDefaultScaleV (1, 1) , fBestQualityScale (1, 1) , fRawToFullScaleH (1.0) , fRawToFullScaleV (1.0) , fBaselineNoise (100, 100) , fNoiseReductionApplied (0, 0) , fNoiseProfile () , fBaselineExposure ( 0, 100) , fBaselineSharpness (100, 100) , fChromaBlurRadius () , fAntiAliasStrength (100, 100) , fLinearResponseLimit (100, 100) , fShadowScale (1, 1) , fColorimetricReference (crSceneReferred) , fColorChannels (0) , fAnalogBalance () , fCameraNeutral () , fCameraWhiteXY () , fCameraCalibration1 () , fCameraCalibration2 () , fCameraCalibrationSignature () , fCameraProfile () , fAsShotProfileName () , fRawImageDigest () , fRawDataUniqueID () , fOriginalRawFileName () , fHasOriginalRawFileData (false) , fOriginalRawFileData () , fOriginalRawFileDigest () , fDNGPrivateData () , fIsMakerNoteSafe (false) , fMakerNote () , fExif () , fOriginalExif () , fIPTCBlock () , fIPTCOffset (kDNGStreamInvalidOffset) , fUsedUTF8forIPTC (false) , fXMP () , fValidEmbeddedXMP (false) , fXMPinSidecar (false) , fXMPisNewer (false) , fLinearizationInfo () , fMosaicInfo () , fOpcodeList1 (1) , fOpcodeList2 (2) , fOpcodeList3 (3) , fStage1Image () , fStage2Image () , fStage3Image () , fStage3Gain (1.0) , fIsPreview (false) , fIsDamaged (false) , fRawImageStage (rawImageStageNone) , fRawImage () { } /*****************************************************************************/ dng_negative::~dng_negative () { // Delete any camera profiles owned by this negative. ClearProfiles (); } /******************************************************************************/ void dng_negative::Initialize () { fExif.Reset (MakeExif ()); fXMP.Reset (MakeXMP ()); } /******************************************************************************/ dng_negative * dng_negative::Make (dng_memory_allocator &allocator) { AutoPtr result (new dng_negative (allocator)); if (!result.Get ()) { ThrowMemoryFull (); } result->Initialize (); return result.Release (); } /******************************************************************************/ void dng_negative::SetBaseOrientation (const dng_orientation &orientation) { fHasBaseOrientation = true; fBaseOrientation = orientation; } /******************************************************************************/ dng_orientation dng_negative::Orientation () const { return BaseOrientation (); } /******************************************************************************/ void dng_negative::ApplyOrientation (const dng_orientation &orientation) { fBaseOrientation += orientation; fXMP->SetOrientation (fBaseOrientation); } /******************************************************************************/ void dng_negative::SetAnalogBalance (const dng_vector &b) { real64 minEntry = b.MinEntry (); if (b.NotEmpty () && minEntry > 0.0) { fAnalogBalance = b; fAnalogBalance.Scale (1.0 / minEntry); fAnalogBalance.Round (1000000.0); } else { fAnalogBalance.Clear (); } } /*****************************************************************************/ real64 dng_negative::AnalogBalance (uint32 channel) const { DNG_ASSERT (channel < ColorChannels (), "Channel out of bounds"); if (channel < fAnalogBalance.Count ()) { return fAnalogBalance [channel]; } return 1.0; } /*****************************************************************************/ dng_urational dng_negative::AnalogBalanceR (uint32 channel) const { dng_urational result; result.Set_real64 (AnalogBalance (channel), 1000000); return result; } /******************************************************************************/ void dng_negative::SetCameraNeutral (const dng_vector &n) { real64 maxEntry = n.MaxEntry (); if (n.NotEmpty () && maxEntry > 0.0) { fCameraNeutral = n; fCameraNeutral.Scale (1.0 / maxEntry); fCameraNeutral.Round (1000000.0); } else { fCameraNeutral.Clear (); } } /*****************************************************************************/ dng_urational dng_negative::CameraNeutralR (uint32 channel) const { dng_urational result; result.Set_real64 (CameraNeutral () [channel], 1000000); return result; } /******************************************************************************/ void dng_negative::SetCameraWhiteXY (const dng_xy_coord &coord) { if (coord.IsValid ()) { fCameraWhiteXY.x = Round_int32 (coord.x * 1000000.0) / 1000000.0; fCameraWhiteXY.y = Round_int32 (coord.y * 1000000.0) / 1000000.0; } else { fCameraWhiteXY.Clear (); } } /*****************************************************************************/ const dng_xy_coord & dng_negative::CameraWhiteXY () const { DNG_ASSERT (HasCameraWhiteXY (), "Using undefined CameraWhiteXY"); return fCameraWhiteXY; } /*****************************************************************************/ void dng_negative::GetCameraWhiteXY (dng_urational &x, dng_urational &y) const { dng_xy_coord coord = CameraWhiteXY (); x.Set_real64 (coord.x, 1000000); y.Set_real64 (coord.y, 1000000); } /*****************************************************************************/ void dng_negative::SetCameraCalibration1 (const dng_matrix &m) { fCameraCalibration1 = m; fCameraCalibration1.Round (10000); } /******************************************************************************/ void dng_negative::SetCameraCalibration2 (const dng_matrix &m) { fCameraCalibration2 = m; fCameraCalibration2.Round (10000); } /******************************************************************************/ void dng_negative::AddProfile (AutoPtr &profile) { // Make sure we have a profile to add. if (!profile.Get ()) { return; } // We must have some profile name. Use "embedded" if nothing else. if (profile->Name ().IsEmpty ()) { profile->SetName (kProfileName_Embedded); } // Special case support for reading older DNG files which did not store // the profile name in the main IFD profile. if (fCameraProfile.size ()) { // See the first profile has a default "embedded" name, and has // the same data as the profile we are adding. if (fCameraProfile [0]->NameIsEmbedded () && fCameraProfile [0]->EqualData (*profile.Get ())) { // If the profile we are deleting was read from DNG // then the new profile should be marked as such also. if (fCameraProfile [0]->WasReadFromDNG ()) { profile->SetWasReadFromDNG (); } // Delete the profile with default name. delete fCameraProfile [0]; fCameraProfile [0] = NULL; fCameraProfile.erase (fCameraProfile.begin ()); } } // Duplicate detection logic. We give a preference to last added profile // so the profiles end up in a more consistent order no matter what profiles // happen to be embedded in the DNG. for (uint32 index = 0; index < (uint32) fCameraProfile.size (); index++) { // Instead of checking for matching fingerprints, we check that the two // profiles have the same color and have the same name. This allows two // profiles that are identical except for copyright string and embed policy // to be considered duplicates. const bool equalColorAndSameName = (fCameraProfile [index]->EqualData (*profile.Get ()) && fCameraProfile [index]->Name () == profile->Name ()); if (equalColorAndSameName) { // If the profile we are deleting was read from DNG // then the new profile should be marked as such also. if (fCameraProfile [index]->WasReadFromDNG ()) { profile->SetWasReadFromDNG (); } // Delete the duplicate profile. delete fCameraProfile [index]; fCameraProfile [index] = NULL; fCameraProfile.erase (fCameraProfile.begin () + index); break; } } // Now add to profile list. fCameraProfile.push_back (NULL); fCameraProfile [fCameraProfile.size () - 1] = profile.Release (); } /******************************************************************************/ void dng_negative::ClearProfiles () { // Delete any camera profiles owned by this negative. for (uint32 index = 0; index < (uint32) fCameraProfile.size (); index++) { if (fCameraProfile [index]) { delete fCameraProfile [index]; fCameraProfile [index] = NULL; } } // Now empty list. fCameraProfile.clear (); } /******************************************************************************/ uint32 dng_negative::ProfileCount () const { return (uint32) fCameraProfile.size (); } /******************************************************************************/ const dng_camera_profile & dng_negative::ProfileByIndex (uint32 index) const { DNG_ASSERT (index < ProfileCount (), "Invalid index for ProfileByIndex"); return *fCameraProfile [index]; } /*****************************************************************************/ const dng_camera_profile * dng_negative::ProfileByID (const dng_camera_profile_id &id, bool useDefaultIfNoMatch) const { uint32 index; // If this negative does not have any profiles, we are not going to // find a match. uint32 profileCount = ProfileCount (); if (profileCount == 0) { return NULL; } // If we have both a profile name and fingerprint, try matching both. if (id.Name ().NotEmpty () && id.Fingerprint ().IsValid ()) { for (index = 0; index < profileCount; index++) { const dng_camera_profile &profile = ProfileByIndex (index); if (id.Name () == profile.Name () && id.Fingerprint () == profile.Fingerprint ()) { return &profile; } } } // If we have a name, try matching that. if (id.Name ().NotEmpty ()) { for (index = 0; index < profileCount; index++) { const dng_camera_profile &profile = ProfileByIndex (index); if (id.Name () == profile.Name ()) { return &profile; } } } // If we have a valid fingerprint, try matching that. if (id.Fingerprint ().IsValid ()) { for (index = 0; index < profileCount; index++) { const dng_camera_profile &profile = ProfileByIndex (index); if (id.Fingerprint () == profile.Fingerprint ()) { return &profile; } } } // Try "upgrading" profile name versions. if (id.Name ().NotEmpty ()) { dng_string baseName; int32 version; SplitCameraProfileName (id.Name (), baseName, version); int32 bestIndex = -1; int32 bestVersion = 0; for (index = 0; index < profileCount; index++) { const dng_camera_profile &profile = ProfileByIndex (index); if (profile.Name ().StartsWith (baseName.Get ())) { dng_string testBaseName; int32 testVersion; SplitCameraProfileName (profile.Name (), testBaseName, testVersion); if (bestIndex == -1 || testVersion > bestVersion) { bestIndex = index; bestVersion = testVersion; } } } if (bestIndex != -1) { return &ProfileByIndex (bestIndex); } } // Did not find a match any way. See if we should return a default value. if (useDefaultIfNoMatch) { return &ProfileByIndex (0); } // Found nothing. return NULL; } /*****************************************************************************/ const dng_camera_profile * dng_negative::CameraProfileToEmbed () const { uint32 index; uint32 count = ProfileCount (); if (count == 0) { return NULL; } // First try to look for the first profile that was already in the DNG // when we read it. for (index = 0; index < count; index++) { const dng_camera_profile &profile (ProfileByIndex (index)); if (profile.WasReadFromDNG ()) { return &profile; } } // Next we look for the first profile that is legal to embed. for (index = 0; index < count; index++) { const dng_camera_profile &profile (ProfileByIndex (index)); if (profile.IsLegalToEmbed ()) { return &profile; } } // Else just return the first profile. return fCameraProfile [0]; } /*****************************************************************************/ dng_color_spec * dng_negative::MakeColorSpec (const dng_camera_profile_id &id) const { dng_color_spec *spec = new dng_color_spec (*this, ProfileByID (id)); if (!spec) { ThrowMemoryFull (); } return spec; } /*****************************************************************************/ void dng_negative::FindRawImageDigest (dng_host &host) const { if (fRawImageDigest.IsNull ()) { const dng_image &image = RawImage (); dng_md5_printer printer; dng_pixel_buffer buffer; buffer.fPlane = 0; buffer.fPlanes = image.Planes (); buffer.fRowStep = image.Planes () * image.Width (); buffer.fColStep = image.Planes (); buffer.fPlaneStep = 1; buffer.fPixelType = image.PixelType (); buffer.fPixelSize = image.PixelSize (); // Sometimes we expand 8-bit data to 16-bit data while reading or // writing, so always compute the digest of 8-bit data as 16-bits. if (buffer.fPixelType == ttByte) { buffer.fPixelType = ttShort; buffer.fPixelSize = 2; } const uint32 kBufferRows = 16; uint32 bufferBytes = kBufferRows * buffer.fRowStep * buffer.fPixelSize; AutoPtr bufferData (host.Allocate (bufferBytes)); buffer.fData = bufferData->Buffer (); dng_rect area; dng_tile_iterator iter (dng_point (kBufferRows, image.Width ()), image.Bounds ()); while (iter.GetOneTile (area)) { host.SniffForAbort (); buffer.fArea = area; image.Get (buffer); uint32 count = buffer.fArea.H () * buffer.fRowStep * buffer.fPixelSize; #if qDNGBigEndian // We need to use the same byte order to compute // the digest, no matter the native order. Little-endian // is more common now, so use that. switch (buffer.fPixelSize) { case 1: break; case 2: { DoSwapBytes16 ((uint16 *) buffer.fData, count >> 1); break; } case 4: { DoSwapBytes32 ((uint32 *) buffer.fData, count >> 2); break; } default: { DNG_REPORT ("Unexpected pixel size"); break; } } #endif printer.Process (buffer.fData, count); } fRawImageDigest = printer.Result (); } } /*****************************************************************************/ void dng_negative::ValidateRawImageDigest (dng_host &host) { if (Stage1Image () && !IsPreview () && fRawImageDigest.IsValid ()) { dng_fingerprint oldDigest = fRawImageDigest; try { fRawImageDigest.Clear (); FindRawImageDigest (host); } catch (...) { fRawImageDigest = oldDigest; throw; } if (oldDigest != fRawImageDigest) { #if qDNGValidate ReportError ("RawImageDigest does not match raw image"); #else // Note that Lightroom 1.4 Windows had a bug that corrupts the // first four bytes of the RawImageDigest tag. So if the last // twelve bytes match, this is very likely the result of the // bug, and not an actual corrupt file. So don't report this // to the user--just fix it. { bool matchLast12 = true; for (uint32 j = 4; j < 16; j++) { matchLast12 = matchLast12 && (oldDigest.data [j] == fRawImageDigest.data [j]); } if (matchLast12) { return; } } // Sometimes Lightroom 1.4 would corrupt more than the first four // bytes, but for all those files that I have seen so far the // resulting first four bytes are 0x08 0x00 0x00 0x00. if (oldDigest.data [0] == 0x08 && oldDigest.data [1] == 0x00 && oldDigest.data [2] == 0x00 && oldDigest.data [3] == 0x00) { return; } SetIsDamaged (true); #endif } } } /*****************************************************************************/ // If the raw data unique ID is missing, compute one based on a MD5 hash of // the raw image data and the model name, plus other commonly changed // data that can affect rendering. void dng_negative::FindRawDataUniqueID (dng_host &host) const { if (fRawDataUniqueID.IsNull ()) { FindRawImageDigest (host); dng_md5_printer_stream printer; printer.SetBigEndian (); // Include the raw image digest in the unique ID. printer.Put (fRawImageDigest.data, 16); // Include model name. printer.Put (ModelName ().Get (), ModelName ().Length ()); // Include default crop area, since DNG Recover Edges can modify // these values and they affect rendering. printer.Put_uint32 (fDefaultCropSizeH.n); printer.Put_uint32 (fDefaultCropSizeH.d); printer.Put_uint32 (fDefaultCropSizeV.n); printer.Put_uint32 (fDefaultCropSizeV.d); printer.Put_uint32 (fDefaultCropOriginH.n); printer.Put_uint32 (fDefaultCropOriginH.d); printer.Put_uint32 (fDefaultCropOriginV.n); printer.Put_uint32 (fDefaultCropOriginV.d); // Include opcode lists, since lens correction utilities can modify // these values and they affect rendering. fOpcodeList1.FingerprintToStream (printer); fOpcodeList2.FingerprintToStream (printer); fOpcodeList3.FingerprintToStream (printer); fRawDataUniqueID = printer.Result (); } } /******************************************************************************/ // Forces recomputation of RawDataUniqueID, useful to call // after modifying the opcode lists, etc. void dng_negative::RecomputeRawDataUniqueID (dng_host &host) { fRawDataUniqueID.Clear (); FindRawDataUniqueID (host); } /******************************************************************************/ void dng_negative::FindOriginalRawFileDigest () const { if (fOriginalRawFileDigest.IsNull () && fOriginalRawFileData.Get ()) { dng_md5_printer printer; printer.Process (fOriginalRawFileData->Buffer (), fOriginalRawFileData->LogicalSize ()); fOriginalRawFileDigest = printer.Result (); } } /*****************************************************************************/ void dng_negative::ValidateOriginalRawFileDigest () { if (fOriginalRawFileDigest.IsValid () && fOriginalRawFileData.Get ()) { dng_fingerprint oldDigest = fOriginalRawFileDigest; try { fOriginalRawFileDigest.Clear (); FindOriginalRawFileDigest (); } catch (...) { fOriginalRawFileDigest = oldDigest; throw; } if (oldDigest != fOriginalRawFileDigest) { #if qDNGValidate ReportError ("OriginalRawFileDigest does not match OriginalRawFileData"); #else SetIsDamaged (true); #endif // Don't "repair" the original image data digest. Once it is // bad, it stays bad. The user cannot tell by looking at the image // whether the damage is acceptable and can be ignored in the // future. fOriginalRawFileDigest = oldDigest; } } } /******************************************************************************/ dng_rect dng_negative::DefaultCropArea (real64 scaleH, real64 scaleV) const { // First compute the area using simple rounding. dng_rect result; result.l = Round_int32 (fDefaultCropOriginH.As_real64 () * fRawToFullScaleH * scaleH); result.t = Round_int32 (fDefaultCropOriginV.As_real64 () * fRawToFullScaleV * scaleV); result.r = result.l + Round_int32 (fDefaultCropSizeH.As_real64 () * fRawToFullScaleH * scaleH); result.b = result.t + Round_int32 (fDefaultCropSizeV.As_real64 () * fRawToFullScaleV * scaleV); // Sometimes the simple rounding causes the resulting default crop // area to slide off the scaled image area. So we force this not // to happen. We only do this if the image is not stubbed. const dng_image *image = Stage3Image (); if (image) { dng_point scaledSize; scaledSize.h = Round_int32 (image->Width () * scaleH); scaledSize.v = Round_int32 (image->Height () * scaleV); if (result.r > scaledSize.h) { result.l -= result.r - scaledSize.h; result.r = scaledSize.h; } if (result.b > scaledSize.v) { result.t -= result.b - scaledSize.v; result.b = scaledSize.v; } } return result; } /******************************************************************************/ void dng_negative::SetShadowScale (const dng_urational &scale) { if (scale.d > 0) { real64 s = scale.As_real64 (); if (s > 0.0 && s <= 1.0) { fShadowScale = scale; } } } /******************************************************************************/ dng_memory_block * dng_negative::BuildExifBlock (const dng_resolution *resolution, bool includeIPTC, bool minimalEXIF, const dng_jpeg_preview *thumbnail) const { dng_memory_stream stream (Allocator ()); { // Create the main IFD dng_tiff_directory mainIFD; // Optionally include the resolution tags. dng_resolution res; if (resolution) { res = *resolution; } tag_urational tagXResolution (tcXResolution, res.fXResolution); tag_urational tagYResolution (tcYResolution, res.fYResolution); tag_uint16 tagResolutionUnit (tcResolutionUnit, res.fResolutionUnit); if (resolution) { mainIFD.Add (&tagXResolution ); mainIFD.Add (&tagYResolution ); mainIFD.Add (&tagResolutionUnit); } // Optionally include IPTC block. tag_iptc tagIPTC (IPTCData (), IPTCLength ()); if (includeIPTC && tagIPTC.Count ()) { mainIFD.Add (&tagIPTC); } // Exif tags. dng_exif exifBlock; if (!minimalEXIF) { exifBlock = *GetExif (); } exif_tag_set exifSet (mainIFD, exifBlock, IsMakerNoteSafe () && !minimalEXIF, MakerNoteData (), MakerNoteLength (), false); // Figure out the Exif IFD offset. uint32 exifOffset = 8 + mainIFD.Size (); exifSet.Locate (exifOffset); // Thumbnail IFD (if any). dng_tiff_directory thumbIFD; tag_uint16 thumbCompression (tcCompression, ccOldJPEG); tag_urational thumbXResolution (tcXResolution, dng_urational (72, 1)); tag_urational thumbYResolution (tcYResolution, dng_urational (72, 1)); tag_uint16 thumbResolutionUnit (tcResolutionUnit, ruInch); tag_uint32 thumbDataOffset (tcJPEGInterchangeFormat , 0); tag_uint32 thumbDataLength (tcJPEGInterchangeFormatLength, 0); if (thumbnail) { thumbIFD.Add (&thumbCompression); thumbIFD.Add (&thumbXResolution); thumbIFD.Add (&thumbYResolution); thumbIFD.Add (&thumbResolutionUnit); thumbIFD.Add (&thumbDataOffset); thumbIFD.Add (&thumbDataLength); thumbDataLength.Set (thumbnail->fCompressedData->LogicalSize ()); uint32 thumbOffset = exifOffset + exifSet.Size (); mainIFD.SetChained (thumbOffset); thumbDataOffset.Set (thumbOffset + thumbIFD.Size ()); } // Don't write anything unless the main IFD has some tags. if (mainIFD.Size () != 0) { // Write TIFF Header. stream.SetWritePosition (0); stream.Put_uint16 (stream.BigEndian () ? byteOrderMM : byteOrderII); stream.Put_uint16 (42); stream.Put_uint32 (8); // Write the IFDs. mainIFD.Put (stream); exifSet.Put (stream); if (thumbnail) { thumbIFD.Put (stream); stream.Put (thumbnail->fCompressedData->Buffer (), thumbnail->fCompressedData->LogicalSize ()); } // Trim the file to this length. stream.Flush (); stream.SetLength (stream.Position ()); } } return stream.AsMemoryBlock (Allocator ()); } /******************************************************************************/ void dng_negative::SetIPTC (AutoPtr &block, uint64 offset) { fIPTCBlock.Reset (block.Release ()); fIPTCOffset = offset; } /******************************************************************************/ void dng_negative::SetIPTC (AutoPtr &block) { SetIPTC (block, kDNGStreamInvalidOffset); } /******************************************************************************/ void dng_negative::ClearIPTC () { fIPTCBlock.Reset (); fIPTCOffset = kDNGStreamInvalidOffset; } /*****************************************************************************/ const void * dng_negative::IPTCData () const { if (fIPTCBlock.Get ()) { return fIPTCBlock->Buffer (); } return NULL; } /*****************************************************************************/ uint32 dng_negative::IPTCLength () const { if (fIPTCBlock.Get ()) { return fIPTCBlock->LogicalSize (); } return 0; } /*****************************************************************************/ uint64 dng_negative::IPTCOffset () const { if (fIPTCBlock.Get ()) { return fIPTCOffset; } return kDNGStreamInvalidOffset; } /*****************************************************************************/ dng_fingerprint dng_negative::IPTCDigest (bool includePadding) const { if (IPTCLength ()) { dng_md5_printer printer; const uint8 *data = (const uint8 *) IPTCData (); uint32 count = IPTCLength (); // Because of some stupid ways of storing the IPTC data, the IPTC // data might be padded with up to three zeros. The official Adobe // logic is to include these zeros in the digest. However, older // versions of the Camera Raw code did not include the padding zeros // in the digest, so we support both methods and allow either to // match. if (!includePadding) { uint32 removed = 0; while ((removed < 3) && (count > 0) && (data [count - 1] == 0)) { removed++; count--; } } printer.Process (data, count); return printer.Result (); } return dng_fingerprint (); } /******************************************************************************/ void dng_negative::RebuildIPTC (bool padForTIFF, bool forceUTF8) { ClearIPTC (); fXMP->RebuildIPTC (*this, padForTIFF, forceUTF8); dng_fingerprint digest = IPTCDigest (); fXMP->SetIPTCDigest (digest); } /*****************************************************************************/ bool dng_negative::SetXMP (dng_host &host, const void *buffer, uint32 count, bool xmpInSidecar, bool xmpIsNewer) { bool result = false; try { AutoPtr tempXMP (MakeXMP ()); tempXMP->Parse (host, buffer, count); fXMP.Reset (tempXMP.Release ()); fXMPinSidecar = xmpInSidecar; fXMPisNewer = xmpIsNewer; result = true; } catch (dng_exception &except) { // Don't ignore transient errors. if (host.IsTransientError (except.ErrorCode ())) { throw; } // Eat other parsing errors. } catch (...) { // Eat unknown parsing exceptions. } return result; } /******************************************************************************/ void dng_negative::SetActiveArea (const dng_rect &area) { NeedLinearizationInfo (); dng_linearization_info &info = *fLinearizationInfo.Get (); info.fActiveArea = area; } /******************************************************************************/ void dng_negative::SetMaskedAreas (uint32 count, const dng_rect *area) { DNG_ASSERT (count <= kMaxMaskedAreas, "Too many masked areas"); NeedLinearizationInfo (); dng_linearization_info &info = *fLinearizationInfo.Get (); info.fMaskedAreaCount = Min_uint32 (count, kMaxMaskedAreas); for (uint32 index = 0; index < info.fMaskedAreaCount; index++) { info.fMaskedArea [index] = area [index]; } } /*****************************************************************************/ void dng_negative::SetLinearization (AutoPtr &curve) { NeedLinearizationInfo (); dng_linearization_info &info = *fLinearizationInfo.Get (); info.fLinearizationTable.Reset (curve.Release ()); } /*****************************************************************************/ void dng_negative::SetBlackLevel (real64 black, int32 plane) { NeedLinearizationInfo (); dng_linearization_info &info = *fLinearizationInfo.Get (); info.fBlackLevelRepeatRows = 1; info.fBlackLevelRepeatCols = 1; if (plane < 0) { for (uint32 j = 0; j < kMaxSamplesPerPixel; j++) { info.fBlackLevel [0] [0] [j] = black; } } else { info.fBlackLevel [0] [0] [plane] = black; } info.RoundBlacks (); } /*****************************************************************************/ void dng_negative::SetQuadBlacks (real64 black0, real64 black1, real64 black2, real64 black3) { NeedLinearizationInfo (); dng_linearization_info &info = *fLinearizationInfo.Get (); info.fBlackLevelRepeatRows = 2; info.fBlackLevelRepeatCols = 2; for (uint32 j = 0; j < kMaxSamplesPerPixel; j++) { info.fBlackLevel [0] [0] [j] = black0; info.fBlackLevel [0] [1] [j] = black1; info.fBlackLevel [1] [0] [j] = black2; info.fBlackLevel [1] [1] [j] = black3; } info.RoundBlacks (); } /*****************************************************************************/ void dng_negative::SetRowBlacks (const real64 *blacks, uint32 count) { if (count) { NeedLinearizationInfo (); dng_linearization_info &info = *fLinearizationInfo.Get (); uint32 byteCount = count * sizeof (real64); info.fBlackDeltaV.Reset (Allocator ().Allocate (byteCount)); DoCopyBytes (blacks, info.fBlackDeltaV->Buffer (), byteCount); info.RoundBlacks (); } else if (fLinearizationInfo.Get ()) { dng_linearization_info &info = *fLinearizationInfo.Get (); info.fBlackDeltaV.Reset (); } } /*****************************************************************************/ void dng_negative::SetColumnBlacks (const real64 *blacks, uint32 count) { if (count) { NeedLinearizationInfo (); dng_linearization_info &info = *fLinearizationInfo.Get (); uint32 byteCount = count * sizeof (real64); info.fBlackDeltaH.Reset (Allocator ().Allocate (byteCount)); DoCopyBytes (blacks, info.fBlackDeltaH->Buffer (), byteCount); info.RoundBlacks (); } else if (fLinearizationInfo.Get ()) { dng_linearization_info &info = *fLinearizationInfo.Get (); info.fBlackDeltaH.Reset (); } } /*****************************************************************************/ uint32 dng_negative::WhiteLevel (uint32 plane) const { if (fLinearizationInfo.Get ()) { const dng_linearization_info &info = *fLinearizationInfo.Get (); return Round_uint32 (info.fWhiteLevel [plane]); } return 0x0FFFF; } /*****************************************************************************/ void dng_negative::SetWhiteLevel (uint32 white, int32 plane) { NeedLinearizationInfo (); dng_linearization_info &info = *fLinearizationInfo.Get (); if (plane < 0) { for (uint32 j = 0; j < kMaxSamplesPerPixel; j++) { info.fWhiteLevel [j] = (real64) white; } } else { info.fWhiteLevel [plane] = (real64) white; } } /******************************************************************************/ void dng_negative::SetColorKeys (ColorKeyCode color0, ColorKeyCode color1, ColorKeyCode color2, ColorKeyCode color3) { NeedMosaicInfo (); dng_mosaic_info &info = *fMosaicInfo.Get (); info.fCFAPlaneColor [0] = color0; info.fCFAPlaneColor [1] = color1; info.fCFAPlaneColor [2] = color2; info.fCFAPlaneColor [3] = color3; } /******************************************************************************/ void dng_negative::SetBayerMosaic (uint32 phase) { NeedMosaicInfo (); dng_mosaic_info &info = *fMosaicInfo.Get (); ColorKeyCode color0 = (ColorKeyCode) info.fCFAPlaneColor [0]; ColorKeyCode color1 = (ColorKeyCode) info.fCFAPlaneColor [1]; ColorKeyCode color2 = (ColorKeyCode) info.fCFAPlaneColor [2]; info.fCFAPatternSize = dng_point (2, 2); switch (phase) { case 0: { info.fCFAPattern [0] [0] = color1; info.fCFAPattern [0] [1] = color0; info.fCFAPattern [1] [0] = color2; info.fCFAPattern [1] [1] = color1; break; } case 1: { info.fCFAPattern [0] [0] = color0; info.fCFAPattern [0] [1] = color1; info.fCFAPattern [1] [0] = color1; info.fCFAPattern [1] [1] = color2; break; } case 2: { info.fCFAPattern [0] [0] = color2; info.fCFAPattern [0] [1] = color1; info.fCFAPattern [1] [0] = color1; info.fCFAPattern [1] [1] = color0; break; } case 3: { info.fCFAPattern [0] [0] = color1; info.fCFAPattern [0] [1] = color2; info.fCFAPattern [1] [0] = color0; info.fCFAPattern [1] [1] = color1; break; } } info.fColorPlanes = 3; info.fCFALayout = 1; } /******************************************************************************/ void dng_negative::SetFujiMosaic (uint32 phase) { NeedMosaicInfo (); dng_mosaic_info &info = *fMosaicInfo.Get (); ColorKeyCode color0 = (ColorKeyCode) info.fCFAPlaneColor [0]; ColorKeyCode color1 = (ColorKeyCode) info.fCFAPlaneColor [1]; ColorKeyCode color2 = (ColorKeyCode) info.fCFAPlaneColor [2]; info.fCFAPatternSize = dng_point (2, 4); switch (phase) { case 0: { info.fCFAPattern [0] [0] = color0; info.fCFAPattern [0] [1] = color1; info.fCFAPattern [0] [2] = color2; info.fCFAPattern [0] [3] = color1; info.fCFAPattern [1] [0] = color2; info.fCFAPattern [1] [1] = color1; info.fCFAPattern [1] [2] = color0; info.fCFAPattern [1] [3] = color1; break; } case 1: { info.fCFAPattern [0] [0] = color2; info.fCFAPattern [0] [1] = color1; info.fCFAPattern [0] [2] = color0; info.fCFAPattern [0] [3] = color1; info.fCFAPattern [1] [0] = color0; info.fCFAPattern [1] [1] = color1; info.fCFAPattern [1] [2] = color2; info.fCFAPattern [1] [3] = color1; break; } } info.fColorPlanes = 3; info.fCFALayout = 2; } /******************************************************************************/ void dng_negative::SetQuadMosaic (uint32 pattern) { // The pattern of the four colors is assumed to be repeat at least every two // columns and eight rows. The pattern is encoded as a 32-bit integer, // with every two bits encoding a color, in scan order for two columns and // eight rows (lsb is first). The usual color coding is: // // 0 = Green // 1 = Magenta // 2 = Cyan // 3 = Yellow // // Examples: // // PowerShot 600 uses 0xe1e4e1e4: // // 0 1 2 3 4 5 // 0 G M G M G M // 1 C Y C Y C Y // 2 M G M G M G // 3 C Y C Y C Y // // PowerShot A5 uses 0x1e4e1e4e: // // 0 1 2 3 4 5 // 0 C Y C Y C Y // 1 G M G M G M // 2 C Y C Y C Y // 3 M G M G M G // // PowerShot A50 uses 0x1b4e4b1e: // // 0 1 2 3 4 5 // 0 C Y C Y C Y // 1 M G M G M G // 2 Y C Y C Y C // 3 G M G M G M // 4 C Y C Y C Y // 5 G M G M G M // 6 Y C Y C Y C // 7 M G M G M G // // PowerShot Pro70 uses 0x1e4b4e1b: // // 0 1 2 3 4 5 // 0 Y C Y C Y C // 1 M G M G M G // 2 C Y C Y C Y // 3 G M G M G M // 4 Y C Y C Y C // 5 G M G M G M // 6 C Y C Y C Y // 7 M G M G M G // // PowerShots Pro90 and G1 use 0xb4b4b4b4: // // 0 1 2 3 4 5 // 0 G M G M G M // 1 Y C Y C Y C NeedMosaicInfo (); dng_mosaic_info &info = *fMosaicInfo.Get (); if (((pattern >> 16) & 0x0FFFF) != (pattern & 0x0FFFF)) { info.fCFAPatternSize = dng_point (8, 2); } else if (((pattern >> 8) & 0x0FF) != (pattern & 0x0FF)) { info.fCFAPatternSize = dng_point (4, 2); } else { info.fCFAPatternSize = dng_point (2, 2); } for (int32 row = 0; row < info.fCFAPatternSize.v; row++) { for (int32 col = 0; col < info.fCFAPatternSize.h; col++) { uint32 index = (pattern >> ((((row << 1) & 14) + (col & 1)) << 1)) & 3; info.fCFAPattern [row] [col] = info.fCFAPlaneColor [index]; } } info.fColorPlanes = 4; info.fCFALayout = 1; } /******************************************************************************/ void dng_negative::SetGreenSplit (uint32 split) { NeedMosaicInfo (); dng_mosaic_info &info = *fMosaicInfo.Get (); info.fBayerGreenSplit = split; } /*****************************************************************************/ void dng_negative::Parse (dng_host &host, dng_stream &stream, dng_info &info) { // Shared info. dng_shared &shared = *(info.fShared.Get ()); // Find IFD holding the main raw information. dng_ifd &rawIFD = *info.fIFD [info.fMainIndex].Get (); // Model name. SetModelName (shared.fUniqueCameraModel.Get ()); // Localized model name. SetLocalName (shared.fLocalizedCameraModel.Get ()); // Base orientation. { uint32 orientation = info.fIFD [0]->fOrientation; if (orientation >= 1 && orientation <= 8) { SetBaseOrientation (dng_orientation::TIFFtoDNG (orientation)); } } // Default crop rectangle. SetDefaultCropSize (rawIFD.fDefaultCropSizeH, rawIFD.fDefaultCropSizeV); SetDefaultCropOrigin (rawIFD.fDefaultCropOriginH, rawIFD.fDefaultCropOriginV); // Default scale. SetDefaultScale (rawIFD.fDefaultScaleH, rawIFD.fDefaultScaleV); // Best quality scale. SetBestQualityScale (rawIFD.fBestQualityScale); // Baseline noise. SetBaselineNoise (shared.fBaselineNoise.As_real64 ()); // NoiseReductionApplied. SetNoiseReductionApplied (shared.fNoiseReductionApplied); // NoiseProfile. SetNoiseProfile (shared.fNoiseProfile); // Baseline exposure. SetBaselineExposure (shared.fBaselineExposure.As_real64 ()); // Baseline sharpness. SetBaselineSharpness (shared.fBaselineSharpness.As_real64 ()); // Chroma blur radius. SetChromaBlurRadius (rawIFD.fChromaBlurRadius); // Anti-alias filter strength. SetAntiAliasStrength (rawIFD.fAntiAliasStrength); // Linear response limit. SetLinearResponseLimit (shared.fLinearResponseLimit.As_real64 ()); // Shadow scale. SetShadowScale (shared.fShadowScale); // Colorimetric reference. SetColorimetricReference (shared.fColorimetricReference); // Color channels. SetColorChannels (shared.fCameraProfile.fColorPlanes); // Analog balance. if (shared.fAnalogBalance.NotEmpty ()) { SetAnalogBalance (shared.fAnalogBalance); } // Camera calibration matrices if (shared.fCameraCalibration1.NotEmpty ()) { SetCameraCalibration1 (shared.fCameraCalibration1); } if (shared.fCameraCalibration2.NotEmpty ()) { SetCameraCalibration2 (shared.fCameraCalibration2); } if (shared.fCameraCalibration1.NotEmpty () || shared.fCameraCalibration2.NotEmpty ()) { SetCameraCalibrationSignature (shared.fCameraCalibrationSignature.Get ()); } // Embedded camera profiles. if (shared.fCameraProfile.fColorPlanes > 1) { if (qDNGValidate || host.NeedsMeta () || host.NeedsImage ()) { // Add profile from main IFD. { AutoPtr profile (new dng_camera_profile ()); dng_camera_profile_info &profileInfo = shared.fCameraProfile; profile->Parse (stream, profileInfo); // The main embedded profile must be valid. if (!profile->IsValid (shared.fCameraProfile.fColorPlanes)) { ThrowBadFormat (); } profile->SetWasReadFromDNG (); AddProfile (profile); } // Extra profiles. for (uint32 index = 0; index < (uint32) shared.fExtraCameraProfiles.size (); index++) { try { AutoPtr profile (new dng_camera_profile ()); dng_camera_profile_info &profileInfo = shared.fExtraCameraProfiles [index]; profile->Parse (stream, profileInfo); if (!profile->IsValid (shared.fCameraProfile.fColorPlanes)) { ThrowBadFormat (); } profile->SetWasReadFromDNG (); AddProfile (profile); } catch (dng_exception &except) { // Don't ignore transient errors. if (host.IsTransientError (except.ErrorCode ())) { throw; } // Eat other parsing errors. #if qDNGValidate ReportWarning ("Unable to parse extra profile"); #endif } } } // As shot profile name. if (shared.fAsShotProfileName.NotEmpty ()) { SetAsShotProfileName (shared.fAsShotProfileName.Get ()); } } // Raw image data digest. if (shared.fRawImageDigest.IsValid ()) { SetRawImageDigest (shared.fRawImageDigest); } // Raw data unique ID. if (shared.fRawDataUniqueID.IsValid ()) { SetRawDataUniqueID (shared.fRawDataUniqueID); } // Original raw file name. if (shared.fOriginalRawFileName.NotEmpty ()) { SetOriginalRawFileName (shared.fOriginalRawFileName.Get ()); } // Original raw file data. if (shared.fOriginalRawFileDataCount) { SetHasOriginalRawFileData (true); if (host.KeepOriginalFile ()) { uint32 count = shared.fOriginalRawFileDataCount; AutoPtr block (host.Allocate (count)); stream.SetReadPosition (shared.fOriginalRawFileDataOffset); stream.Get (block->Buffer (), count); SetOriginalRawFileData (block); SetOriginalRawFileDigest (shared.fOriginalRawFileDigest); ValidateOriginalRawFileDigest (); } } // DNG private data. if (shared.fDNGPrivateDataCount && (host.SaveDNGVersion () != dngVersion_None)) { uint32 length = shared.fDNGPrivateDataCount; AutoPtr block (host.Allocate (length)); stream.SetReadPosition (shared.fDNGPrivateDataOffset); stream.Get (block->Buffer (), length); SetPrivateData (block); } // Hand off EXIF metadata to negative. fExif.Reset (info.fExif.Release ()); // Parse linearization info. NeedLinearizationInfo (); fLinearizationInfo.Get ()->Parse (host, stream, info); // Parse mosaic info. if (rawIFD.fPhotometricInterpretation == piCFA) { NeedMosaicInfo (); fMosaicInfo.Get ()->Parse (host, stream, info); } } /*****************************************************************************/ void dng_negative::PostParse (dng_host &host, dng_stream &stream, dng_info &info) { // Shared info. dng_shared &shared = *(info.fShared.Get ()); if (host.NeedsMeta ()) { // MakerNote. if (shared.fMakerNoteCount) { // See if we know if the MakerNote is safe or not. SetMakerNoteSafety (shared.fMakerNoteSafety == 1); // If the MakerNote is safe, preserve it as a MakerNote. if (IsMakerNoteSafe ()) { AutoPtr block (host.Allocate (shared.fMakerNoteCount)); stream.SetReadPosition (shared.fMakerNoteOffset); stream.Get (block->Buffer (), shared.fMakerNoteCount); SetMakerNote (block); } } // IPTC metadata. if (shared.fIPTC_NAA_Count) { AutoPtr block (host.Allocate (shared.fIPTC_NAA_Count)); stream.SetReadPosition (shared.fIPTC_NAA_Offset); uint64 iptcOffset = stream.PositionInOriginalFile(); stream.Get (block->Buffer (), block->LogicalSize ()); SetIPTC (block, iptcOffset); } // XMP metadata. if (shared.fXMPCount) { AutoPtr block (host.Allocate (shared.fXMPCount)); stream.SetReadPosition (shared.fXMPOffset); stream.Get (block->Buffer (), block->LogicalSize ()); fValidEmbeddedXMP = SetXMP (host, block->Buffer (), block->LogicalSize ()); #if qDNGValidate if (!fValidEmbeddedXMP) { ReportError ("The embedded XMP is invalid"); } #endif } // Color info. if (!IsMonochrome ()) { // If the ColorimetricReference is the ICC profile PCS, // then the data must be already be white balanced to the // ICC profile PCS white point. if (ColorimetricReference () == crICCProfilePCS) { ClearCameraNeutral (); SetCameraWhiteXY (PCStoXY ()); } else { // AsShotNeutral. if (shared.fAsShotNeutral.Count () == ColorChannels ()) { SetCameraNeutral (shared.fAsShotNeutral); } // AsShotWhiteXY. if (shared.fAsShotWhiteXY.IsValid () && !HasCameraNeutral ()) { SetCameraWhiteXY (shared.fAsShotWhiteXY); } } } } } /*****************************************************************************/ void dng_negative::SynchronizeMetadata () { if (!fOriginalExif.Get ()) { fOriginalExif.Reset (fExif->Clone ()); } fXMP->ValidateMetadata (); fXMP->IngestIPTC (*this, fXMPisNewer); fXMP->SyncExif (*fExif.Get ()); fXMP->SyncOrientation (*this, fXMPinSidecar); } /*****************************************************************************/ void dng_negative::UpdateDateTime (const dng_date_time_info &dt) { fExif->UpdateDateTime (dt); fXMP->UpdateDateTime (dt); } /*****************************************************************************/ void dng_negative::UpdateDateTimeToNow () { dng_date_time_info dt; CurrentDateTimeAndZone (dt); UpdateDateTime (dt); } /*****************************************************************************/ bool dng_negative::SetFourColorBayer () { if (ColorChannels () != 3) { return false; } if (!fMosaicInfo.Get ()) { return false; } if (!fMosaicInfo.Get ()->SetFourColorBayer ()) { return false; } SetColorChannels (4); if (fCameraNeutral.Count () == 3) { dng_vector n (4); n [0] = fCameraNeutral [0]; n [1] = fCameraNeutral [1]; n [2] = fCameraNeutral [2]; n [3] = fCameraNeutral [1]; fCameraNeutral = n; } fCameraCalibration1.Clear (); fCameraCalibration2.Clear (); fCameraCalibrationSignature.Clear (); for (uint32 index = 0; index < (uint32) fCameraProfile.size (); index++) { fCameraProfile [index]->SetFourColorBayer (); } return true; } /*****************************************************************************/ const dng_image & dng_negative::RawImage () const { if (fRawImage.Get ()) { return *fRawImage.Get (); } if (fStage1Image.Get ()) { return *fStage1Image.Get (); } DNG_ASSERT (fStage3Image.Get (), "dng_negative::RawImage with no raw image"); return *fStage3Image.Get (); } /*****************************************************************************/ void dng_negative::ReadStage1Image (dng_host &host, dng_stream &stream, dng_info &info) { dng_ifd &rawIFD = *info.fIFD [info.fMainIndex].Get (); fStage1Image.Reset (host.Make_dng_image (rawIFD.Bounds (), rawIFD.fSamplesPerPixel, rawIFD.PixelType ())); rawIFD.ReadImage (host, stream, *fStage1Image.Get ()); - // We are are reading the main image, we should read the opcode lists + // We are reading the main image, we should read the opcode lists // also. if (rawIFD.fOpcodeList1Count) { #if qDNGValidate if (gVerbose) { printf ("\nParsing OpcodeList1: "); } #endif fOpcodeList1.Parse (host, stream, rawIFD.fOpcodeList1Count, rawIFD.fOpcodeList1Offset); } if (rawIFD.fOpcodeList2Count) { #if qDNGValidate if (gVerbose) { printf ("\nParsing OpcodeList2: "); } #endif fOpcodeList2.Parse (host, stream, rawIFD.fOpcodeList2Count, rawIFD.fOpcodeList2Offset); } if (rawIFD.fOpcodeList3Count) { #if qDNGValidate if (gVerbose) { printf ("\nParsing OpcodeList3: "); } #endif fOpcodeList3.Parse (host, stream, rawIFD.fOpcodeList3Count, rawIFD.fOpcodeList3Offset); } } /*****************************************************************************/ void dng_negative::SetStage1Image (AutoPtr &image) { fStage1Image.Reset (image.Release ()); } /*****************************************************************************/ void dng_negative::SetStage2Image (AutoPtr &image) { fStage2Image.Reset (image.Release ()); } /*****************************************************************************/ void dng_negative::SetStage3Image (AutoPtr &image) { fStage3Image.Reset (image.Release ()); } /*****************************************************************************/ void dng_negative::DoBuildStage2 (dng_host &host, uint32 pixelType) { dng_image &stage1 = *fStage1Image.Get (); dng_linearization_info &info = *fLinearizationInfo.Get (); fStage2Image.Reset (host.Make_dng_image (info.fActiveArea.Size (), stage1.Planes (), pixelType)); info.Linearize (host, stage1, *fStage2Image.Get ()); } /*****************************************************************************/ void dng_negative::BuildStage2Image (dng_host &host, uint32 pixelType) { // If reading the negative to save in DNG format, figure out // when to grab a copy of the raw data. if (host.SaveDNGVersion () != dngVersion_None) { if (fOpcodeList3.MinVersion (false) > host.SaveDNGVersion () || fOpcodeList3.AlwaysApply ()) { fRawImageStage = rawImageStagePostOpcode3; } else if (host.SaveLinearDNG (*this)) { // If the opcode list 3 has optional tags that are beyond the // the minimum version, and we are saving a linear DNG anyway, // then go ahead and apply them. if (fOpcodeList3.MinVersion (true) > host.SaveDNGVersion ()) { fRawImageStage = rawImageStagePostOpcode3; } else { fRawImageStage = rawImageStagePreOpcode3; } } else if (fOpcodeList2.MinVersion (false) > host.SaveDNGVersion () || fOpcodeList2.AlwaysApply ()) { fRawImageStage = rawImageStagePostOpcode2; } else if (fOpcodeList1.MinVersion (false) > host.SaveDNGVersion () || fOpcodeList1.AlwaysApply ()) { fRawImageStage = rawImageStagePostOpcode1; } else { fRawImageStage = rawImageStagePreOpcode1; } } // Grab clone of raw image if required. if (fRawImageStage == rawImageStagePreOpcode1) { fRawImage.Reset (fStage1Image->Clone ()); } else { // If we are not keeping the most raw image, we need // to recompute the raw image digest. ClearRawImageDigest (); } // Process opcode list 1. host.ApplyOpcodeList (fOpcodeList1, *this, fStage1Image); // See if we are done with the opcode list 1. if (fRawImageStage > rawImageStagePreOpcode1) { fOpcodeList1.Clear (); } // Grab clone of raw image if required. if (fRawImageStage == rawImageStagePostOpcode1) { fRawImage.Reset (fStage1Image->Clone ()); } // Finalize linearization info. { NeedLinearizationInfo (); dng_linearization_info &info = *fLinearizationInfo.Get (); info.PostParse (host, *this); } // Perform the linearization. DoBuildStage2 (host, pixelType); // Delete the stage1 image now that we have computed the stage 2 image. fStage1Image.Reset (); // Are we done with the linearization info. if (fRawImageStage > rawImageStagePostOpcode1) { ClearLinearizationInfo (); } // Process opcode list 2. host.ApplyOpcodeList (fOpcodeList2, *this, fStage2Image); // See if we are done with the opcode list 2. if (fRawImageStage > rawImageStagePostOpcode1) { fOpcodeList2.Clear (); } // Grab clone of raw image if required. if (fRawImageStage == rawImageStagePostOpcode2) { fRawImage.Reset (fStage2Image->Clone ()); } } /*****************************************************************************/ void dng_negative::DoInterpolateStage3 (dng_host &host, int32 srcPlane) { dng_image &stage2 = *fStage2Image.Get (); dng_mosaic_info &info = *fMosaicInfo.Get (); dng_point downScale = info.DownScale (host.MinimumSize (), host.PreferredSize (), host.CropFactor ()); if (downScale != dng_point (1, 1)) { SetIsPreview (true); } dng_point dstSize = info.DstSize (downScale); fStage3Image.Reset (host.Make_dng_image (dng_rect (dstSize), info.fColorPlanes, stage2.PixelType ())); if (srcPlane < 0 || srcPlane >= (int32) stage2.Planes ()) { srcPlane = 0; } info.Interpolate (host, *this, stage2, *fStage3Image.Get (), downScale, srcPlane); } /*****************************************************************************/ // Interpolate and merge a multi-channel CFA image. void dng_negative::DoMergeStage3 (dng_host &host) { // The DNG SDK does not provide multi-channel CFA image merging code. // It just grabs the first channel and uses that. DoInterpolateStage3 (host, 0); // Just grabbing the first channel would often result in the very // bright image using the baseline exposure value. fStage3Gain = pow (2.0, BaselineExposure ()); } /*****************************************************************************/ void dng_negative::DoBuildStage3 (dng_host &host, int32 srcPlane) { // If we don't have a mosaic pattern, then just move the stage 2 // image on to stage 3. dng_mosaic_info *info = fMosaicInfo.Get (); if (!info || !info->IsColorFilterArray ()) { fStage3Image.Reset (fStage2Image.Release ()); } else { // Remember the size of the stage 2 image. dng_point stage2_size = fStage2Image->Size (); // Special case multi-channel CFA interpolation. if ((fStage2Image->Planes () > 1) && (srcPlane < 0)) { DoMergeStage3 (host); } // Else do a single channel interpolation. else { DoInterpolateStage3 (host, srcPlane); } // Calculate the ratio of the stage 3 image size to stage 2 image size. dng_point stage3_size = fStage3Image->Size (); fRawToFullScaleH = (real64) stage3_size.h / (real64) stage2_size.h; fRawToFullScaleV = (real64) stage3_size.v / (real64) stage2_size.v; } } /*****************************************************************************/ void dng_negative::BuildStage3Image (dng_host &host, int32 srcPlane) { // Finalize the mosaic information. dng_mosaic_info *info = fMosaicInfo.Get (); if (info) { info->PostParse (host, *this); } // Do the interpolation as required. DoBuildStage3 (host, srcPlane); // Delete the stage2 image now that we have computed the stage 3 image. fStage2Image.Reset (); // Are we done with the mosaic info? if (fRawImageStage >= rawImageStagePreOpcode3) { ClearMosaicInfo (); // To support saving linear DNG files, to need to account for // and upscaling during interpolation. if (fRawToFullScaleH > 1.0) { uint32 adjust = Round_uint32 (fRawToFullScaleH); fDefaultCropSizeH .n *= adjust; fDefaultCropOriginH.n *= adjust; fDefaultScaleH .d *= adjust; fRawToFullScaleH /= (real64) adjust; } if (fRawToFullScaleV > 1.0) { uint32 adjust = Round_uint32 (fRawToFullScaleV); fDefaultCropSizeV .n *= adjust; fDefaultCropOriginV.n *= adjust; fDefaultScaleV .d *= adjust; fRawToFullScaleV /= (real64) adjust; } } // Grab clone of raw image if required. if (fRawImageStage == rawImageStagePreOpcode3) { fRawImage.Reset (fStage3Image->Clone ()); } // Process opcode list 3. host.ApplyOpcodeList (fOpcodeList3, *this, fStage3Image); // See if we are done with the opcode list 3. if (fRawImageStage > rawImageStagePreOpcode3) { fOpcodeList3.Clear (); } // Don't need to grab a copy of raw data at this stage since // it is kept around as the stage 3 image. } /*****************************************************************************/ dng_exif * dng_negative::MakeExif () { dng_exif *exif = new dng_exif (); if (!exif) { ThrowMemoryFull (); } return exif; } /*****************************************************************************/ dng_xmp * dng_negative::MakeXMP () { dng_xmp *xmp = new dng_xmp (Allocator ()); if (!xmp) { ThrowMemoryFull (); } return xmp; } /*****************************************************************************/ dng_linearization_info * dng_negative::MakeLinearizationInfo () { dng_linearization_info *info = new dng_linearization_info (); if (!info) { ThrowMemoryFull (); } return info; } /*****************************************************************************/ void dng_negative::NeedLinearizationInfo () { if (!fLinearizationInfo.Get ()) { fLinearizationInfo.Reset (MakeLinearizationInfo ()); } } /*****************************************************************************/ dng_mosaic_info * dng_negative::MakeMosaicInfo () { dng_mosaic_info *info = new dng_mosaic_info (); if (!info) { ThrowMemoryFull (); } return info; } /*****************************************************************************/ void dng_negative::NeedMosaicInfo () { if (!fMosaicInfo.Get ()) { fMosaicInfo.Reset (MakeMosaicInfo ()); } } /*****************************************************************************/ diff --git a/libs/dngwriter/extra/xmp_sdk/XMPCore/ParseRDF.cpp b/libs/dngwriter/extra/xmp_sdk/XMPCore/ParseRDF.cpp index cd2b4ef1c9..c04742ce2c 100644 --- a/libs/dngwriter/extra/xmp_sdk/XMPCore/ParseRDF.cpp +++ b/libs/dngwriter/extra/xmp_sdk/XMPCore/ParseRDF.cpp @@ -1,1344 +1,1344 @@ // ================================================================================================= // Copyright 2002-2008 Adobe Systems Incorporated // All Rights Reserved. // // NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms // of the Adobe license agreement accompanying it. // ================================================================================================= #include "XMP_Environment.h" // ! This must be the first include! #include "XMPCore_Impl.hpp" #include "ExpatAdapter.hpp" #include #if DEBUG #include #endif using namespace std; namespace DngXmpSdk { #if XMP_WinBuild #pragma warning ( disable : 4189 ) // local variable is initialized but not referenced #pragma warning ( disable : 4505 ) // unreferenced local function has been removed #endif // ================================================================================================= // *** This might be faster and use less memory as a state machine. A big advantage of building an // *** XML tree though is easy lookahead during the recursive descent processing. // *** It would be nice to give a line number or byte offset in the exception messages. // 7 RDF/XML Grammar (from http://www.w3.org/TR/rdf-syntax-grammar/#section-Infoset-Grammar) // // 7.1 Grammar summary // // 7.2.2 coreSyntaxTerms // rdf:RDF | rdf:ID | rdf:about | rdf:parseType | rdf:resource | rdf:nodeID | rdf:datatype // // 7.2.3 syntaxTerms // coreSyntaxTerms | rdf:Description | rdf:li // // 7.2.4 oldTerms // rdf:aboutEach | rdf:aboutEachPrefix | rdf:bagID // // 7.2.5 nodeElementURIs // anyURI - ( coreSyntaxTerms | rdf:li | oldTerms ) // // 7.2.6 propertyElementURIs // anyURI - ( coreSyntaxTerms | rdf:Description | oldTerms ) // // 7.2.7 propertyAttributeURIs // anyURI - ( coreSyntaxTerms | rdf:Description | rdf:li | oldTerms ) // // 7.2.8 doc // root ( document-element == RDF, children == list ( RDF ) ) // // 7.2.9 RDF // start-element ( URI == rdf:RDF, attributes == set() ) // nodeElementList // end-element() // // 7.2.10 nodeElementList // ws* ( nodeElement ws* )* // // 7.2.11 nodeElement // start-element ( URI == nodeElementURIs, // attributes == set ( ( idAttr | nodeIdAttr | aboutAttr )?, propertyAttr* ) ) // propertyEltList // end-element() // // 7.2.12 ws // A text event matching white space defined by [XML] definition White Space Rule [3] S in section Common Syntactic Constructs. // // 7.2.13 propertyEltList // ws* ( propertyElt ws* )* // // 7.2.14 propertyElt // resourcePropertyElt | literalPropertyElt | parseTypeLiteralPropertyElt | // parseTypeResourcePropertyElt | parseTypeCollectionPropertyElt | parseTypeOtherPropertyElt | emptyPropertyElt // // 7.2.15 resourcePropertyElt // start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) ) // ws* nodeElement ws* // end-element() // // 7.2.16 literalPropertyElt // start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, datatypeAttr?) ) // text() // end-element() // // 7.2.17 parseTypeLiteralPropertyElt // start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseLiteral ) ) // literal // end-element() // // 7.2.18 parseTypeResourcePropertyElt // start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseResource ) ) // propertyEltList // end-element() // // 7.2.19 parseTypeCollectionPropertyElt // start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseCollection ) ) // nodeElementList // end-element() // // 7.2.20 parseTypeOtherPropertyElt // start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseOther ) ) // propertyEltList // end-element() // // 7.2.21 emptyPropertyElt // start-element ( URI == propertyElementURIs, // attributes == set ( idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) ) // end-element() // // 7.2.22 idAttr // attribute ( URI == rdf:ID, string-value == rdf-id ) // // 7.2.23 nodeIdAttr // attribute ( URI == rdf:nodeID, string-value == rdf-id ) // // 7.2.24 aboutAttr // attribute ( URI == rdf:about, string-value == URI-reference ) // // 7.2.25 propertyAttr // attribute ( URI == propertyAttributeURIs, string-value == anyString ) // // 7.2.26 resourceAttr // attribute ( URI == rdf:resource, string-value == URI-reference ) // // 7.2.27 datatypeAttr // attribute ( URI == rdf:datatype, string-value == URI-reference ) // // 7.2.28 parseLiteral // attribute ( URI == rdf:parseType, string-value == "Literal") // // 7.2.29 parseResource // attribute ( URI == rdf:parseType, string-value == "Resource") // // 7.2.30 parseCollection // attribute ( URI == rdf:parseType, string-value == "Collection") // // 7.2.31 parseOther // attribute ( URI == rdf:parseType, string-value == anyString - ("Resource" | "Literal" | "Collection") ) // // 7.2.32 URI-reference // An RDF URI Reference. // // 7.2.33 literal // Any XML element content that is allowed according to [XML] definition Content of Elements Rule [43] content // in section 3.1 Start-Tags, End-Tags, and Empty-Element Tags. // // 7.2.34 rdf-id // An attribute string-value matching any legal [XML-NS] token NCName. // ================================================================================================= // Primary Parsing Functions // ========================= // // Each of these is responsible for recognizing an RDF syntax production and adding the appropriate // structure to the XMP tree. They simply return for success, failures will throw an exception. static void RDF_RDF ( XMP_Node * xmpTree, const XML_Node & xmlNode ); static void RDF_NodeElementList ( XMP_Node * xmpParent, const XML_Node & xmlParent, bool isTopLevel ); static void RDF_NodeElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ); static void RDF_NodeElementAttrs ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ); static void RDF_PropertyElementList ( XMP_Node * xmpParent, const XML_Node & xmlParent, bool isTopLevel ); enum { kIsTopLevel = true, kNotTopLevel = false }; static void RDF_PropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ); static void RDF_ResourcePropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ); static void RDF_LiteralPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ); static void RDF_ParseTypeLiteralPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ); static void RDF_ParseTypeResourcePropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ); static void RDF_ParseTypeCollectionPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ); static void RDF_ParseTypeOtherPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ); static void RDF_EmptyPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ); // ================================================================================================= typedef XMP_Uns8 RDFTermKind; // *** Logic might be safer with just masks. enum { kRDFTerm_Other = 0, kRDFTerm_RDF = 1, // Start of coreSyntaxTerms. kRDFTerm_ID = 2, kRDFTerm_about = 3, kRDFTerm_parseType = 4, kRDFTerm_resource = 5, kRDFTerm_nodeID = 6, kRDFTerm_datatype = 7, // End of coreSyntaxTerms. kRDFTerm_Description = 8, // Start of additions for syntaxTerms. - kRDFTerm_li = 9, // End of of additions for syntaxTerms. + kRDFTerm_li = 9, // End of additions for syntaxTerms. kRDFTerm_aboutEach = 10, // Start of oldTerms. kRDFTerm_aboutEachPrefix = 11, kRDFTerm_bagID = 12, // End of oldTerms. kRDFTerm_FirstCore = kRDFTerm_RDF, kRDFTerm_LastCore = kRDFTerm_datatype, kRDFTerm_FirstSyntax = kRDFTerm_FirstCore, // ! Yes, the syntax terms include the core terms. kRDFTerm_LastSyntax = kRDFTerm_li, kRDFTerm_FirstOld = kRDFTerm_aboutEach, kRDFTerm_LastOld = kRDFTerm_bagID }; enum { kRDFMask_Other = 1 << kRDFTerm_Other, kRDFMask_RDF = 1 << kRDFTerm_RDF, kRDFMask_ID = 1 << kRDFTerm_ID, kRDFMask_about = 1 << kRDFTerm_about, kRDFMask_parseType = 1 << kRDFTerm_parseType, kRDFMask_resource = 1 << kRDFTerm_resource, kRDFMask_nodeID = 1 << kRDFTerm_nodeID, kRDFMask_datatype = 1 << kRDFTerm_datatype, kRDFMask_Description = 1 << kRDFTerm_Description, kRDFMask_li = 1 << kRDFTerm_li, kRDFMask_aboutEach = 1 << kRDFTerm_aboutEach, kRDFMask_aboutEachPrefix = 1 << kRDFTerm_aboutEachPrefix, kRDFMask_bagID = 1 << kRDFTerm_bagID }; enum { kRDF_HasValueElem = 0x10000000UL // ! Contains rdf:value child. Must fit within kXMP_ImplReservedMask! }; // ------------------------------------------------------------------------------------------------- // GetRDFTermKind // -------------- static RDFTermKind GetRDFTermKind ( const XMP_VarString & name ) { RDFTermKind term = kRDFTerm_Other; // Arranged to hopefully minimize the parse time for large XMP. if ( (name.size() > 4) && (strncmp ( name.c_str(), "rdf:", 4 ) == 0) ) { if ( name == "rdf:li" ) { term = kRDFTerm_li; } else if ( name == "rdf:parseType" ) { term = kRDFTerm_parseType; } else if ( name == "rdf:Description" ) { term = kRDFTerm_Description; } else if ( name == "rdf:about" ) { term = kRDFTerm_about; } else if ( name == "rdf:resource" ) { term = kRDFTerm_resource; } else if ( name == "rdf:RDF" ) { term = kRDFTerm_RDF; } else if ( name == "rdf:ID" ) { term = kRDFTerm_ID; } else if ( name == "rdf:nodeID" ) { term = kRDFTerm_nodeID; } else if ( name == "rdf:datatype" ) { term = kRDFTerm_datatype; } else if ( name == "rdf:aboutEach" ) { term = kRDFTerm_aboutEach; } else if ( name == "rdf:aboutEachPrefix" ) { term = kRDFTerm_aboutEachPrefix; } else if ( name == "rdf:bagID" ) { term = kRDFTerm_bagID; } } return term; } // GetRDFTermKind // ================================================================================================= // ------------------------------------------------------------------------------------------------- // IsCoreSyntaxTerm // ---------------- // // 7.2.2 coreSyntaxTerms // rdf:RDF | rdf:ID | rdf:about | rdf:parseType | rdf:resource | rdf:nodeID | rdf:datatype static bool IsCoreSyntaxTerm ( RDFTermKind term ) { if ( (kRDFTerm_FirstCore <= term) && (term <= kRDFTerm_LastCore) ) return true; return false; } // IsCoreSyntaxTerm // ------------------------------------------------------------------------------------------------- // IsSyntaxTerm // ------------ // // 7.2.3 syntaxTerms // coreSyntaxTerms | rdf:Description | rdf:li static bool IsSyntaxTerm ( RDFTermKind term ) { if ( (kRDFTerm_FirstSyntax <= term) && (term <= kRDFTerm_LastSyntax) ) return true; return false; } // IsSyntaxTerm // ------------------------------------------------------------------------------------------------- // IsOldTerm // --------- // // 7.2.4 oldTerms // rdf:aboutEach | rdf:aboutEachPrefix | rdf:bagID static bool IsOldTerm ( RDFTermKind term ) { if ( (kRDFTerm_FirstOld <= term) && (term <= kRDFTerm_LastOld) ) return true; return false; } // IsOldTerm // ------------------------------------------------------------------------------------------------- // IsNodeElementName // ----------------- // // 7.2.5 nodeElementURIs // anyURI - ( coreSyntaxTerms | rdf:li | oldTerms ) static bool IsNodeElementName ( RDFTermKind term ) { if ( (term == kRDFTerm_li) || IsOldTerm ( term ) ) return false; return (! IsCoreSyntaxTerm ( term )); } // IsNodeElementName // ------------------------------------------------------------------------------------------------- // IsPropertyElementName // --------------------- // // 7.2.6 propertyElementURIs // anyURI - ( coreSyntaxTerms | rdf:Description | oldTerms ) static bool IsPropertyElementName ( RDFTermKind term ) { if ( (term == kRDFTerm_Description) || IsOldTerm ( term ) ) return false; return (! IsCoreSyntaxTerm ( term )); } // IsPropertyElementName // ------------------------------------------------------------------------------------------------- // IsPropertyAttributeName // ----------------------- // // 7.2.7 propertyAttributeURIs // anyURI - ( coreSyntaxTerms | rdf:Description | rdf:li | oldTerms ) static bool IsPropertyAttributeName ( RDFTermKind term ) { if ( (term == kRDFTerm_Description) || (term == kRDFTerm_li) || IsOldTerm ( term ) ) return false; return (! IsCoreSyntaxTerm ( term )); } // IsPropertyAttributeName // ================================================================================================= // AddChildNode // ============ static XMP_Node * AddChildNode ( XMP_Node * xmpParent, const XML_Node & xmlNode, const XMP_StringPtr value, bool isTopLevel ) { #if 0 cout << "AddChildNode, parent = " << xmpParent->name << ", child = " << xmlNode.name; cout << ", value = \"" << value << '"'; if ( isTopLevel ) cout << ", top level"; cout << endl; #endif if ( xmlNode.ns.empty() ) { XMP_Throw ( "XML namespace required for all elements and attributes", kXMPErr_BadRDF ); } XMP_StringPtr childName = xmlNode.name.c_str(); const bool isArrayItem = (xmlNode.name == "rdf:li"); const bool isValueNode = (xmlNode.name == "rdf:value"); XMP_OptionBits childOptions = 0; if ( isTopLevel ) { // Lookup the schema node, adjust the XMP parent pointer. XMP_Assert ( xmpParent->parent == 0 ); // Incoming parent must be the tree root. XMP_Node * schemaNode = FindSchemaNode ( xmpParent, xmlNode.ns.c_str(), kXMP_CreateNodes ); if ( schemaNode->options & kXMP_NewImplicitNode ) schemaNode->options ^= kXMP_NewImplicitNode; // Clear the implicit node bit. // *** Should use "opt &= ~flag" (no conditional), need runtime check for proper 32 bit code. xmpParent = schemaNode; // If this is an alias set the isAlias flag in the node and the hasAliases flag in the tree. if ( sRegisteredAliasMap->find ( xmlNode.name ) != sRegisteredAliasMap->end() ) { childOptions |= kXMP_PropIsAlias; schemaNode->parent->options |= kXMP_PropHasAliases; } } // Make sure that this is not a duplicate of a named node. if ( ! (isArrayItem | isValueNode) ) { if ( FindChildNode ( xmpParent, childName, kXMP_ExistingOnly ) != 0 ) { XMP_Throw ( "Duplicate property or field node", kXMPErr_BadXMP ); } } // Add the new child to the XMP parent node. XMP_Node * newChild = new XMP_Node ( xmpParent, childName, value, childOptions ); if ( (! isValueNode) || xmpParent->children.empty() ) { xmpParent->children.push_back ( newChild ); } else { xmpParent->children.insert ( xmpParent->children.begin(), newChild ); } if ( isValueNode ) { if ( isTopLevel || (! (xmpParent->options & kXMP_PropValueIsStruct)) ) XMP_Throw ( "Misplaced rdf:value element", kXMPErr_BadRDF ); xmpParent->options |= kRDF_HasValueElem; } if ( isArrayItem ) { if ( ! (xmpParent->options & kXMP_PropValueIsArray) ) XMP_Throw ( "Misplaced rdf:li element", kXMPErr_BadRDF ); newChild->name = kXMP_ArrayItemName; #if 0 // *** XMP_DebugBuild newChild->_namePtr = newChild->name.c_str(); #endif } return newChild; } // AddChildNode // ================================================================================================= // AddQualifierNode // ================ static XMP_Node * AddQualifierNode ( XMP_Node * xmpParent, const XMP_VarString & name, const XMP_VarString & value ) { #if 0 cout << "AddQualifierNode, parent = " << xmpParent->name << ", name = " << name; cout << ", value = \"" << value << '"' << endl; #endif const bool isLang = (name == "xml:lang"); const bool isType = (name == "rdf:type"); XMP_Node * newQual = 0; newQual = new XMP_Node ( xmpParent, name, value, kXMP_PropIsQualifier ); if ( ! (isLang | isType) ) { xmpParent->qualifiers.push_back ( newQual ); } else if ( isLang ) { if ( xmpParent->qualifiers.empty() ) { xmpParent->qualifiers.push_back ( newQual ); } else { xmpParent->qualifiers.insert ( xmpParent->qualifiers.begin(), newQual ); } xmpParent->options |= kXMP_PropHasLang; } else { XMP_Assert ( isType ); if ( xmpParent->qualifiers.empty() ) { xmpParent->qualifiers.push_back ( newQual ); } else { size_t offset = 0; if ( XMP_PropHasLang ( xmpParent->options ) ) offset = 1; xmpParent->qualifiers.insert ( xmpParent->qualifiers.begin()+offset, newQual ); } xmpParent->options |= kXMP_PropHasType; } xmpParent->options |= kXMP_PropHasQualifiers; return newQual; } // AddQualifierNode // ================================================================================================= // AddQualifierNode // ================ static XMP_Node * AddQualifierNode ( XMP_Node * xmpParent, const XML_Node & attr ) { if ( attr.ns.empty() ) { XMP_Throw ( "XML namespace required for all elements and attributes", kXMPErr_BadRDF ); } return AddQualifierNode ( xmpParent, attr.name, attr.value ); } // AddQualifierNode // ================================================================================================= // FixupQualifiedNode // ================== // // The parent is an RDF pseudo-struct containing an rdf:value field. Fix the XMP data model. The // rdf:value node must be the first child, the other children are qualifiers. The form, value, and // children of the rdf:value node are the real ones. The rdf:value node's qualifiers must be added // to the others. static void FixupQualifiedNode ( XMP_Node * xmpParent ) { size_t qualNum, qualLim; size_t childNum, childLim; XMP_Enforce ( (xmpParent->options & kXMP_PropValueIsStruct) && (! xmpParent->children.empty()) ); XMP_Node * valueNode = xmpParent->children[0]; XMP_Enforce ( valueNode->name == "rdf:value" ); xmpParent->qualifiers.reserve ( xmpParent->qualifiers.size() + xmpParent->children.size() + valueNode->qualifiers.size() ); // Move the qualifiers on the value node to the parent. Make sure an xml:lang qualifier stays at // the front. Check for duplicate names between the value node's qualifiers and the parent's // children. The parent's children are about to become qualifiers. Check here, between the // groups. Intra-group duplicates are caught by AddChildNode. qualNum = 0; qualLim = valueNode->qualifiers.size(); if ( valueNode->options & kXMP_PropHasLang ) { if ( xmpParent->options & kXMP_PropHasLang ) XMP_Throw ( "Redundant xml:lang for rdf:value element", kXMPErr_BadXMP ); XMP_Node * langQual = valueNode->qualifiers[0]; XMP_Assert ( langQual->name == "xml:lang" ); langQual->parent = xmpParent; xmpParent->options |= kXMP_PropHasLang; if ( xmpParent->qualifiers.empty() ) { xmpParent->qualifiers.push_back ( langQual ); // *** Should use utilities to add qual & set parent. } else { xmpParent->qualifiers.insert ( xmpParent->qualifiers.begin(), langQual ); } valueNode->qualifiers[0] = 0; // We just moved it to the parent. qualNum = 1; // Start the remaining copy after the xml:lang qualifier. } for ( ; qualNum != qualLim; ++qualNum ) { XMP_Node * currQual = valueNode->qualifiers[qualNum]; if ( FindChildNode ( xmpParent, currQual->name.c_str(), kXMP_ExistingOnly ) != 0 ) { XMP_Throw ( "Duplicate qualifier node", kXMPErr_BadXMP ); } currQual->parent = xmpParent; xmpParent->qualifiers.push_back ( currQual ); valueNode->qualifiers[qualNum] = 0; // We just moved it to the parent. } valueNode->qualifiers.clear(); // ! There should be nothing but null pointers. // Change the parent's other children into qualifiers. This loop starts at 1, child 0 is the // rdf:value node. Put xml:lang at the front, append all others. for ( childNum = 1, childLim = xmpParent->children.size(); childNum != childLim; ++childNum ) { XMP_Node * currQual = xmpParent->children[childNum]; bool isLang = (currQual->name == "xml:lang"); currQual->options |= kXMP_PropIsQualifier; currQual->parent = xmpParent; if ( isLang ) { if ( xmpParent->options & kXMP_PropHasLang ) XMP_Throw ( "Duplicate xml:lang qualifier", kXMPErr_BadXMP ); xmpParent->options |= kXMP_PropHasLang; } else if ( currQual->name == "rdf:type" ) { xmpParent->options |= kXMP_PropHasType; } if ( (! isLang) || xmpParent->qualifiers.empty() ) { xmpParent->qualifiers.push_back ( currQual ); } else { xmpParent->qualifiers.insert ( xmpParent->qualifiers.begin(), currQual ); } xmpParent->children[childNum] = 0; // We just moved it to the qualifers. } if ( ! xmpParent->qualifiers.empty() ) xmpParent->options |= kXMP_PropHasQualifiers; // Move the options and value last, other checks need the parent's original options. Move the // value node's children to be the parent's children. Delete the now useless value node. XMP_Assert ( xmpParent->options & (kXMP_PropValueIsStruct | kRDF_HasValueElem) ); xmpParent->options &= ~ (kXMP_PropValueIsStruct | kRDF_HasValueElem); xmpParent->options |= valueNode->options; xmpParent->value.swap ( valueNode->value ); #if 0 // *** XMP_DebugBuild xmpParent->_valuePtr = xmpParent->value.c_str(); #endif xmpParent->children[0] = 0; // ! Remove the value node itself before the swap. xmpParent->children.swap ( valueNode->children ); for ( size_t childNum = 0, childLim = xmpParent->children.size(); childNum != childLim; ++childNum ) { XMP_Node * currChild = xmpParent->children[childNum]; currChild->parent = xmpParent; } delete valueNode; } // FixupQualifiedNode // ================================================================================================= // ProcessRDF // ========== // // Parse the XML tree of the RDF and build the corresponding XMP tree. // *** Throw an exception if no XMP is found? By option? // *** Do parsing exceptions cause the partial tree to be deleted? void ProcessRDF ( XMP_Node * xmpTree, const XML_Node & rdfNode, XMP_OptionBits options ) { IgnoreParam(options); RDF_RDF ( xmpTree, rdfNode ); } // ProcessRDF // ================================================================================================= // RDF_RDF // ======= // // 7.2.9 RDF // start-element ( URI == rdf:RDF, attributes == set() ) // nodeElementList // end-element() // // The top level rdf:RDF node. It can only have xmlns attributes, which have already been removed // during construction of the XML tree. static void RDF_RDF ( XMP_Node * xmpTree, const XML_Node & xmlNode ) { if ( ! xmlNode.attrs.empty() ) XMP_Throw ( "Invalid attributes of rdf:RDF element", kXMPErr_BadRDF ); RDF_NodeElementList ( xmpTree, xmlNode, kIsTopLevel ); } // RDF_RDF // ================================================================================================= // RDF_NodeElementList // =================== // // 7.2.10 nodeElementList // ws* ( nodeElement ws* )* static void RDF_NodeElementList ( XMP_Node * xmpParent, const XML_Node & xmlParent, bool isTopLevel ) { XMP_Assert ( isTopLevel ); XML_cNodePos currChild = xmlParent.content.begin(); // *** Change these loops to the indexed pattern. XML_cNodePos endChild = xmlParent.content.end(); for ( ; currChild != endChild; ++currChild ) { if ( (*currChild)->IsWhitespaceNode() ) continue; RDF_NodeElement ( xmpParent, **currChild, isTopLevel ); } } // RDF_NodeElementList // ================================================================================================= // RDF_NodeElement // =============== // // 7.2.5 nodeElementURIs // anyURI - ( coreSyntaxTerms | rdf:li | oldTerms ) // // 7.2.11 nodeElement // start-element ( URI == nodeElementURIs, // attributes == set ( ( idAttr | nodeIdAttr | aboutAttr )?, propertyAttr* ) ) // propertyEltList // end-element() // // A node element URI is rdf:Description or anything else that is not an RDF term. static void RDF_NodeElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ) { RDFTermKind nodeTerm = GetRDFTermKind ( xmlNode.name ); if ( (nodeTerm != kRDFTerm_Description) && (nodeTerm != kRDFTerm_Other) ) { XMP_Throw ( "Node element must be rdf:Description or typedNode", kXMPErr_BadRDF ); } if ( isTopLevel && (nodeTerm == kRDFTerm_Other) ) { XMP_Throw ( "Top level typedNode not allowed", kXMPErr_BadXMP ); } else { RDF_NodeElementAttrs ( xmpParent, xmlNode, isTopLevel ); RDF_PropertyElementList ( xmpParent, xmlNode, isTopLevel ); } } // RDF_NodeElement // ================================================================================================= // RDF_NodeElementAttrs // ==================== // // 7.2.7 propertyAttributeURIs // anyURI - ( coreSyntaxTerms | rdf:Description | rdf:li | oldTerms ) // // 7.2.11 nodeElement // start-element ( URI == nodeElementURIs, // attributes == set ( ( idAttr | nodeIdAttr | aboutAttr )?, propertyAttr* ) ) // propertyEltList // end-element() // // Process the attribute list for an RDF node element. A property attribute URI is anything other // than an RDF term. The rdf:ID and rdf:nodeID attributes are simply ignored, as are rdf:about // attributes on inner nodes. static const XMP_OptionBits kExclusiveAttrMask = (kRDFMask_ID | kRDFMask_nodeID | kRDFMask_about); static void RDF_NodeElementAttrs ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ) { XMP_OptionBits exclusiveAttrs = 0; // Used to detect attributes that are mutually exclusive. XML_cNodePos currAttr = xmlNode.attrs.begin(); XML_cNodePos endAttr = xmlNode.attrs.end(); for ( ; currAttr != endAttr; ++currAttr ) { RDFTermKind attrTerm = GetRDFTermKind ( (*currAttr)->name ); switch ( attrTerm ) { case kRDFTerm_ID : case kRDFTerm_nodeID : case kRDFTerm_about : if ( exclusiveAttrs & kExclusiveAttrMask ) XMP_Throw ( "Mutally exclusive about, ID, nodeID attributes", kXMPErr_BadRDF ); exclusiveAttrs |= (1 << attrTerm); if ( isTopLevel && (attrTerm == kRDFTerm_about) ) { // This is the rdf:about attribute on a top level node. Set the XMP tree name if // it doesn't have a name yet. Make sure this name matches the XMP tree name. XMP_Assert ( xmpParent->parent == 0 ); // Must be the tree root node. if ( xmpParent->name.empty() ) { xmpParent->name = (*currAttr)->value; } else if ( ! (*currAttr)->value.empty() ) { if ( xmpParent->name != (*currAttr)->value ) XMP_Throw ( "Mismatched top level rdf:about values", kXMPErr_BadXMP ); } } break; case kRDFTerm_Other : AddChildNode ( xmpParent, **currAttr, (*currAttr)->value.c_str(), isTopLevel ); break; default : XMP_Throw ( "Invalid nodeElement attribute", kXMPErr_BadRDF ); break; } } } // RDF_NodeElementAttrs // ================================================================================================= // RDF_PropertyElementList // ======================= // // 7.2.13 propertyEltList // ws* ( propertyElt ws* )* static void RDF_PropertyElementList ( XMP_Node * xmpParent, const XML_Node & xmlParent, bool isTopLevel ) { XML_cNodePos currChild = xmlParent.content.begin(); XML_cNodePos endChild = xmlParent.content.end(); for ( ; currChild != endChild; ++currChild ) { if ( (*currChild)->IsWhitespaceNode() ) continue; if ( (*currChild)->kind != kElemNode ) { XMP_Throw ( "Expected property element node not found", kXMPErr_BadRDF ); } RDF_PropertyElement ( xmpParent, **currChild, isTopLevel ); } } // RDF_PropertyElementList // ================================================================================================= // RDF_PropertyElement // =================== // // 7.2.14 propertyElt // resourcePropertyElt | literalPropertyElt | parseTypeLiteralPropertyElt | // parseTypeResourcePropertyElt | parseTypeCollectionPropertyElt | parseTypeOtherPropertyElt | emptyPropertyElt // // 7.2.15 resourcePropertyElt // start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) ) // ws* nodeElement ws* // end-element() // // 7.2.16 literalPropertyElt // start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, datatypeAttr?) ) // text() // end-element() // // 7.2.17 parseTypeLiteralPropertyElt // start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseLiteral ) ) // literal // end-element() // // 7.2.18 parseTypeResourcePropertyElt // start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseResource ) ) // propertyEltList // end-element() // // 7.2.19 parseTypeCollectionPropertyElt // start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseCollection ) ) // nodeElementList // end-element() // // 7.2.20 parseTypeOtherPropertyElt // start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseOther ) ) // propertyEltList // end-element() // // 7.2.21 emptyPropertyElt // start-element ( URI == propertyElementURIs, // attributes == set ( idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) ) // end-element() // // The various property element forms are not distinguished by the XML element name, but by their // attributes for the most part. The exceptions are resourcePropertyElt and literalPropertyElt. They // are distinguished by their XML element content. // // NOTE: The RDF syntax does not explicitly include the xml:lang attribute although it can appear in // many of these. We have to allow for it in the attibute counts below. static void RDF_PropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ) { RDFTermKind nodeTerm = GetRDFTermKind ( xmlNode.name ); if ( ! IsPropertyElementName ( nodeTerm ) ) XMP_Throw ( "Invalid property element name", kXMPErr_BadRDF ); if ( xmlNode.attrs.size() > 3 ) { // Only an emptyPropertyElt can have more than 3 attributes. RDF_EmptyPropertyElement ( xmpParent, xmlNode, isTopLevel ); } else { // Look through the attributes for one that isn't rdf:ID or xml:lang, it will usually tell // what we should be dealing with. The called routines must verify their specific syntax! XML_cNodePos currAttr = xmlNode.attrs.begin(); XML_cNodePos endAttr = xmlNode.attrs.end(); XMP_VarString * attrName = 0; for ( ; currAttr != endAttr; ++currAttr ) { attrName = &((*currAttr)->name); if ( (*attrName != "xml:lang") && (*attrName != "rdf:ID") ) break; } if ( currAttr != endAttr ) { XMP_Assert ( attrName != 0 ); XMP_VarString& attrValue = (*currAttr)->value; if ( *attrName == "rdf:datatype" ) { RDF_LiteralPropertyElement ( xmpParent, xmlNode, isTopLevel ); } else if ( *attrName != "rdf:parseType" ) { RDF_EmptyPropertyElement ( xmpParent, xmlNode, isTopLevel ); } else if ( attrValue == "Literal" ) { RDF_ParseTypeLiteralPropertyElement ( xmpParent, xmlNode, isTopLevel ); } else if ( attrValue == "Resource" ) { RDF_ParseTypeResourcePropertyElement ( xmpParent, xmlNode, isTopLevel ); } else if ( attrValue == "Collection" ) { RDF_ParseTypeCollectionPropertyElement ( xmpParent, xmlNode, isTopLevel ); } else { RDF_ParseTypeOtherPropertyElement ( xmpParent, xmlNode, isTopLevel ); } } else { // Only rdf:ID and xml:lang, could be a resourcePropertyElt, a literalPropertyElt, or an. // emptyPropertyElt. Look at the child XML nodes to decide which. if ( xmlNode.content.empty() ) { RDF_EmptyPropertyElement ( xmpParent, xmlNode, isTopLevel ); } else { XML_cNodePos currChild = xmlNode.content.begin(); XML_cNodePos endChild = xmlNode.content.end(); for ( ; currChild != endChild; ++currChild ) { if ( (*currChild)->kind != kCDataNode ) break; } if ( currChild == endChild ) { RDF_LiteralPropertyElement ( xmpParent, xmlNode, isTopLevel ); } else { RDF_ResourcePropertyElement ( xmpParent, xmlNode, isTopLevel ); } } } } } // RDF_PropertyElement // ================================================================================================= // RDF_ResourcePropertyElement // =========================== // // 7.2.15 resourcePropertyElt // start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) ) // ws* nodeElement ws* // end-element() // // This handles structs using an rdf:Description node, arrays using rdf:Bag/Seq/Alt, and typedNodes. // It also catches and cleans up qualified properties written with rdf:Description and rdf:value. static void RDF_ResourcePropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ) { if ( isTopLevel && (xmlNode.name == "iX:changes") ) return; // Strip old "punchcard" chaff. XMP_Node * newCompound = AddChildNode ( xmpParent, xmlNode, "", isTopLevel ); XML_cNodePos currAttr = xmlNode.attrs.begin(); XML_cNodePos endAttr = xmlNode.attrs.end(); for ( ; currAttr != endAttr; ++currAttr ) { XMP_VarString & attrName = (*currAttr)->name; if ( attrName == "xml:lang" ) { AddQualifierNode ( newCompound, **currAttr ); } else if ( attrName == "rdf:ID" ) { continue; // Ignore all rdf:ID attributes. } else { XMP_Throw ( "Invalid attribute for resource property element", kXMPErr_BadRDF ); } } XML_cNodePos currChild = xmlNode.content.begin(); XML_cNodePos endChild = xmlNode.content.end(); for ( ; currChild != endChild; ++currChild ) { if ( ! (*currChild)->IsWhitespaceNode() ) break; } if ( currChild == endChild ) XMP_Throw ( "Missing child of resource property element", kXMPErr_BadRDF ); if ( (*currChild)->kind != kElemNode ) XMP_Throw ( "Children of resource property element must be XML elements", kXMPErr_BadRDF ); if ( (*currChild)->name == "rdf:Bag" ) { newCompound->options |= kXMP_PropValueIsArray; } else if ( (*currChild)->name == "rdf:Seq" ) { newCompound->options |= kXMP_PropValueIsArray | kXMP_PropArrayIsOrdered; } else if ( (*currChild)->name == "rdf:Alt" ) { newCompound->options |= kXMP_PropValueIsArray | kXMP_PropArrayIsOrdered | kXMP_PropArrayIsAlternate; } else { newCompound->options |= kXMP_PropValueIsStruct; if ( (*currChild)->name != "rdf:Description" ) { XMP_VarString typeName ( (*currChild)->ns ); size_t colonPos = (*currChild)->name.find_first_of(':'); if ( colonPos == XMP_VarString::npos ) XMP_Throw ( "All XML elements must be in a namespace", kXMPErr_BadXMP ); typeName.append ( (*currChild)->name, colonPos, XMP_VarString::npos ); AddQualifierNode ( newCompound, XMP_VarString("rdf:type"), typeName ); } } RDF_NodeElement ( newCompound, **currChild, kNotTopLevel ); if ( newCompound->options & kRDF_HasValueElem ) { FixupQualifiedNode ( newCompound ); } else if ( newCompound->options & kXMP_PropArrayIsAlternate ) { DetectAltText ( newCompound ); } for ( ++currChild; currChild != endChild; ++currChild ) { if ( ! (*currChild)->IsWhitespaceNode() ) XMP_Throw ( "Invalid child of resource property element", kXMPErr_BadRDF ); } } // RDF_ResourcePropertyElement // ================================================================================================= // RDF_LiteralPropertyElement // ========================== // // 7.2.16 literalPropertyElt // start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, datatypeAttr?) ) // text() // end-element() // // Add a leaf node with the text value and qualifiers for the attributes. static void RDF_LiteralPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ) { XMP_Node * newChild = AddChildNode ( xmpParent, xmlNode, "", isTopLevel ); XML_cNodePos currAttr = xmlNode.attrs.begin(); XML_cNodePos endAttr = xmlNode.attrs.end(); for ( ; currAttr != endAttr; ++currAttr ) { XMP_VarString & attrName = (*currAttr)->name; if ( attrName == "xml:lang" ) { AddQualifierNode ( newChild, **currAttr ); } else if ( (attrName == "rdf:ID") || (attrName == "rdf:datatype") ) { continue; // Ignore all rdf:ID and rdf:datatype attributes. } else { XMP_Throw ( "Invalid attribute for literal property element", kXMPErr_BadRDF ); } } XML_cNodePos currChild = xmlNode.content.begin(); XML_cNodePos endChild = xmlNode.content.end(); size_t textSize = 0; for ( ; currChild != endChild; ++currChild ) { if ( (*currChild)->kind != kCDataNode ) XMP_Throw ( "Invalid child of literal property element", kXMPErr_BadRDF ); textSize += (*currChild)->value.size(); } newChild->value.reserve ( textSize ); for ( currChild = xmlNode.content.begin(); currChild != endChild; ++currChild ) { newChild->value += (*currChild)->value; } #if 0 // *** XMP_DebugBuild newChild->_valuePtr = newChild->value.c_str(); #endif } // RDF_LiteralPropertyElement // ================================================================================================= // RDF_ParseTypeLiteralPropertyElement // =================================== // // 7.2.17 parseTypeLiteralPropertyElt // start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseLiteral ) ) // literal // end-element() static void RDF_ParseTypeLiteralPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ) { IgnoreParam(xmpParent); IgnoreParam(xmlNode); IgnoreParam(isTopLevel); XMP_Throw ( "ParseTypeLiteral property element not allowed", kXMPErr_BadXMP ); } // RDF_ParseTypeLiteralPropertyElement // ================================================================================================= // RDF_ParseTypeResourcePropertyElement // ==================================== // // 7.2.18 parseTypeResourcePropertyElt // start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseResource ) ) // propertyEltList // end-element() // // Add a new struct node with a qualifier for the possible rdf:ID attribute. Then process the XML // child nodes to get the struct fields. static void RDF_ParseTypeResourcePropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ) { XMP_Node * newStruct = AddChildNode ( xmpParent, xmlNode, "", isTopLevel ); newStruct->options |= kXMP_PropValueIsStruct; XML_cNodePos currAttr = xmlNode.attrs.begin(); XML_cNodePos endAttr = xmlNode.attrs.end(); for ( ; currAttr != endAttr; ++currAttr ) { XMP_VarString & attrName = (*currAttr)->name; if ( attrName == "rdf:parseType" ) { continue; // ! The caller ensured the value is "Resource". } else if ( attrName == "xml:lang" ) { AddQualifierNode ( newStruct, **currAttr ); } else if ( attrName == "rdf:ID" ) { continue; // Ignore all rdf:ID attributes. } else { XMP_Throw ( "Invalid attribute for ParseTypeResource property element", kXMPErr_BadRDF ); } } RDF_PropertyElementList ( newStruct, xmlNode, kNotTopLevel ); if ( newStruct->options & kRDF_HasValueElem ) FixupQualifiedNode ( newStruct ); // *** Need to look for arrays using rdf:Description and rdf:type. } // RDF_ParseTypeResourcePropertyElement // ================================================================================================= // RDF_ParseTypeCollectionPropertyElement // ====================================== // // 7.2.19 parseTypeCollectionPropertyElt // start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseCollection ) ) // nodeElementList // end-element() static void RDF_ParseTypeCollectionPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ) { IgnoreParam(xmpParent); IgnoreParam(xmlNode); IgnoreParam(isTopLevel); XMP_Throw ( "ParseTypeCollection property element not allowed", kXMPErr_BadXMP ); } // RDF_ParseTypeCollectionPropertyElement // ================================================================================================= // RDF_ParseTypeOtherPropertyElement // ================================= // // 7.2.20 parseTypeOtherPropertyElt // start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseOther ) ) // propertyEltList // end-element() static void RDF_ParseTypeOtherPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ) { IgnoreParam(xmpParent); IgnoreParam(xmlNode); IgnoreParam(isTopLevel); XMP_Throw ( "ParseTypeOther property element not allowed", kXMPErr_BadXMP ); } // RDF_ParseTypeOtherPropertyElement // ================================================================================================= // RDF_EmptyPropertyElement // ======================== // // 7.2.21 emptyPropertyElt // start-element ( URI == propertyElementURIs, // attributes == set ( idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) ) // end-element() // // // // // // // An emptyPropertyElt is an element with no contained content, just a possibly empty set of // attributes. An emptyPropertyElt can represent three special cases of simple XMP properties: a // simple property with an empty value (ns:Prop1), a simple property whose value is a URI // (ns:Prop2), or a simple property with simple qualifiers (ns:Prop3). An emptyPropertyElt can also // represent an XMP struct whose fields are all simple and unqualified (ns:Prop4). // // It is an error to use both rdf:value and rdf:resource - that can lead to invalid RDF in the // verbose form written using a literalPropertyElt. // // The XMP mapping for an emptyPropertyElt is a bit different from generic RDF, partly for // design reasons and partly for historical reasons. The XMP mapping rules are: // 1. If there is an rdf:value attribute then this is a simple property with a text value. // All other attributes are qualifiers. // 2. If there is an rdf:resource attribute then this is a simple property with a URI value. // All other attributes are qualifiers. // 3. If there are no attributes other than xml:lang, rdf:ID, or rdf:nodeID then this is a simple // property with an empty value. // 4. Otherwise this is a struct, the attributes other than xml:lang, rdf:ID, or rdf:nodeID are fields. static void RDF_EmptyPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel ) { bool hasPropertyAttrs = false; bool hasResourceAttr = false; bool hasNodeIDAttr = false; bool hasValueAttr = false; const XML_Node * valueNode = 0; // ! Can come from rdf:value or rdf:resource. if ( ! xmlNode.content.empty() ) XMP_Throw ( "Nested content not allowed with rdf:resource or property attributes", kXMPErr_BadRDF ); // First figure out what XMP this maps to and remember the XML node for a simple value. XML_cNodePos currAttr = xmlNode.attrs.begin(); XML_cNodePos endAttr = xmlNode.attrs.end(); for ( ; currAttr != endAttr; ++currAttr ) { RDFTermKind attrTerm = GetRDFTermKind ( (*currAttr)->name ); switch ( attrTerm ) { case kRDFTerm_ID : // Nothing to do. break; case kRDFTerm_resource : if ( hasNodeIDAttr ) XMP_Throw ( "Empty property element can't have both rdf:resource and rdf:nodeID", kXMPErr_BadRDF ); if ( hasValueAttr ) XMP_Throw ( "Empty property element can't have both rdf:value and rdf:resource", kXMPErr_BadXMP ); hasResourceAttr = true; if ( ! hasValueAttr ) valueNode = *currAttr; break; case kRDFTerm_nodeID : if ( hasResourceAttr ) XMP_Throw ( "Empty property element can't have both rdf:resource and rdf:nodeID", kXMPErr_BadRDF ); hasNodeIDAttr = true; break; case kRDFTerm_Other : if ( (*currAttr)->name == "rdf:value" ) { if ( hasResourceAttr ) XMP_Throw ( "Empty property element can't have both rdf:value and rdf:resource", kXMPErr_BadXMP ); hasValueAttr = true; valueNode = *currAttr; } else if ( (*currAttr)->name != "xml:lang" ) { hasPropertyAttrs = true; } break; default : XMP_Throw ( "Unrecognized attribute of empty property element", kXMPErr_BadRDF ); break; } } // Create the right kind of child node and visit the attributes again to add the fields or qualifiers. // ! Because of implementation vagaries, the xmpParent is the tree root for top level properties. // ! The schema is found, created if necessary, by AddChildNode. XMP_Node * childNode = AddChildNode ( xmpParent, xmlNode, "", isTopLevel ); bool childIsStruct = false; if ( hasValueAttr | hasResourceAttr ) { childNode->value = valueNode->value; if ( ! hasValueAttr ) childNode->options |= kXMP_PropValueIsURI; // ! Might have both rdf:value and rdf:resource. } else if ( hasPropertyAttrs ) { childNode->options |= kXMP_PropValueIsStruct; childIsStruct = true; } currAttr = xmlNode.attrs.begin(); endAttr = xmlNode.attrs.end(); for ( ; currAttr != endAttr; ++currAttr ) { if ( *currAttr == valueNode ) continue; // Skip the rdf:value or rdf:resource attribute holding the value. RDFTermKind attrTerm = GetRDFTermKind ( (*currAttr)->name ); switch ( attrTerm ) { case kRDFTerm_ID : case kRDFTerm_nodeID : break; // Ignore all rdf:ID and rdf:nodeID attributes.w case kRDFTerm_resource : AddQualifierNode ( childNode, **currAttr ); break; case kRDFTerm_Other : if ( (! childIsStruct) || (*currAttr)->name == "xml:lang" ) { AddQualifierNode ( childNode, **currAttr ); } else { AddChildNode ( childNode, **currAttr, (*currAttr)->value.c_str(), false ); } break; default : XMP_Throw ( "Unrecognized attribute of empty property element", kXMPErr_BadRDF ); break; } } } // RDF_EmptyPropertyElement } // namespace DngXmpSdk // ================================================================================================= diff --git a/libs/widgets/common/searchtextbar.h b/libs/widgets/common/searchtextbar.h index 1cd6fbe349..3fb96440de 100644 --- a/libs/widgets/common/searchtextbar.h +++ b/libs/widgets/common/searchtextbar.h @@ -1,208 +1,208 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2007-11-25 * Description : a bar used to search a string. * * Copyright (C) 2007-2017 by Gilles Caulier * * 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, 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. * * ============================================================ */ #ifndef SEARCH_TEXT_BAR_H #define SEARCH_TEXT_BAR_H // Qt includes #include #include #include // KDE includes #include // Local includes #include "digikam_export.h" #include "modelcompleter.h" #include "statesavingobject.h" namespace Digikam { class AbstractAlbumModel; class AlbumFilterModel; class DIGIKAM_EXPORT SearchTextSettings { public: SearchTextSettings() { caseSensitive = Qt::CaseInsensitive; } Qt::CaseSensitivity caseSensitive; QString text; }; bool DIGIKAM_EXPORT operator==(const SearchTextSettings& a, const SearchTextSettings& b); /** * A text input for searching entries with visual feedback. * Can be used on QAbstractItemModels. * * @author Gilles Caulier */ class DIGIKAM_EXPORT SearchTextBar : public QLineEdit, public StateSavingObject { Q_OBJECT public: /** * Possible highlighting states a SearchTextBar can have. */ enum HighlightState { /** * No highlighting at all. Background is colored in a neutral way * according to the theme. */ NEUTRAL, /** * The background color of the text input indicates that a result was * found. */ HAS_RESULT, /** * The background color indicates that no result was found. */ NO_RESULT }; public: explicit SearchTextBar(QWidget* const parent, const QString& name, const QString& msg=i18n("Search...")); ~SearchTextBar(); void setTextQueryCompletion(bool b); bool hasTextQueryCompletion() const; /** * Tells whether highlighting for found search results shall be used or not * (green and red). * * Default behavior has highlighting enabled. * * @param highlight true activates green and red highlighting, * with false the normal widget background * color will be displayed always */ void setHighlightOnResult(bool highlight); /** * If the given model is != null, the model is used to populate the * completion for this text field. * * @param model to fill from or null for manual mode * @param uniqueIdRole a role for which the model will return a unique integer for each entry * @param displayRole the role to retrieve the text for completion, default is Qt::DisplayRole. */ void setModel(QAbstractItemModel* model, int uniqueIdRole, int displayRole = Qt::DisplayRole); void setModel(AbstractAlbumModel* model); /** * Sets the filter model this text bar shall use to invoke filtering on and * reading the result for highlighting from. * * @param filterModel filter model to use for filtering. null * means there is no model to use and external * connections need to be created with * signalSearchTextSettings and slotSearchResult */ void setFilterModel(AlbumFilterModel* filterModel); /** * Tells the current highlighting state of the text input indicated via the * background color. * * @return current highlight state */ HighlightState getCurrentHighlightState() const; /** * Indicate whether this search text bar can be toggled to between case- - * sensitive and -insensitive or or if always case-insensitive shall be + * sensitive and -insensitive or if always case-insensitive shall be * used. * * @param b if true the user can decide the toggle between * case sensitivity, on false every search is case- * insensitive */ void setCaseSensitive(bool b); bool hasCaseSensitive() const; void setSearchTextSettings(const SearchTextSettings& settings); SearchTextSettings searchTextSettings() const; ModelCompleter* completerModel() const; Q_SIGNALS: void signalSearchTextSettings(const SearchTextSettings& settings); public Q_SLOTS: void slotSearchResult(bool match); private Q_SLOTS: void slotTextChanged(const QString&); protected: virtual void doLoadState(); virtual void doSaveState(); private: void contextMenuEvent(QContextMenuEvent* e); /** * If hasCaseSensitive returns true this tells the search * text bar whether to ignore case or not. * * @param ignore if true, case is ignored in the emitted * search text settings and the completion */ void setIgnoreCase(bool ignore); private: class Private; Private* const d; }; } // namespace Digikam #endif // SEARCH_TEXT_BAR_H diff --git a/utilities/assistants/printcreator/wizard/advprintcustomdlg.cpp b/utilities/assistants/printcreator/wizard/advprintcustomdlg.cpp index 76e89dcba8..3364318c03 100644 --- a/utilities/assistants/printcreator/wizard/advprintcustomdlg.cpp +++ b/utilities/assistants/printcreator/wizard/advprintcustomdlg.cpp @@ -1,113 +1,113 @@ /* =============================================================== * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2010-10-01 * Description : Dialog to allow a custom page layout * * Copyright (C) 2010-2012 by Angelo Naselli * Copyright (C) 2006-2017 by Gilles Caulier * * 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, 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. * * ============================================================== */ #include "advprintcustomdlg.h" // KDE includes #include #include namespace Digikam { AdvPrintCustomLayoutDlg::AdvPrintCustomLayoutDlg(QWidget* const parent) : QDialog(parent) { setupUi(this); connect(m_doneButton, SIGNAL(clicked()), this, SLOT(accept())); m_photoGridCheck->setToolTip(i18n("Choose your grid size")); m_photoGridCheck->setWhatsThis(i18n("Choose your grid size")); m_gridRows->setToolTip(i18n("Number of rows")); m_gridRows->setWhatsThis(i18n("Insert number of rows")); m_gridColumns->setToolTip(i18n("Number of columns")); m_gridColumns->setWhatsThis(i18n("Insert number of columns")); m_fitAsManyCheck->setToolTip(i18n("Choose to have a custom photo size album")); m_fitAsManyCheck->setWhatsThis(i18n("Choose to have a custom photo size album")); m_photoHeight->setToolTip(i18n("Photo height")); m_photoHeight->setWhatsThis(i18n("Insert photo height")); m_photoWidth->setToolTip(i18n("Photo width")); m_photoWidth->setWhatsThis(i18n("Insert photo width")); - m_autorotate->setToolTip(i18n("Rotate photo automatically on layout accorndigly " + m_autorotate->setToolTip(i18n("Rotate photo automatically on layout accordingly " "with camera orientation information")); } AdvPrintCustomLayoutDlg:: ~AdvPrintCustomLayoutDlg() { } void AdvPrintCustomLayoutDlg::readSettings() { KConfig config; KConfigGroup group = config.group(QLatin1String("PrintCreator")); QSize gridSize = group.readEntry(QLatin1String("Custom-gridSize"), QSize(3, 8)); m_gridRows->setValue(gridSize.width()); m_gridColumns->setValue(gridSize.height()); QSize photoSize = group.readEntry (QLatin1String("Custom-photoSize"), QSize(5, 4)); m_photoHeight->setValue(photoSize.height()); m_photoWidth->setValue(photoSize.width()); int index = group.readEntry(QLatin1String("Custom-photoUnits"), 0); m_photoUnits->setCurrentIndex(index); bool autorotate = group.readEntry(QLatin1String("Custom-autorotate"), false); m_autorotate->setChecked(autorotate); int choice = group.readEntry(QLatin1String("Custom-choice"), (int)PHOTO_GRID); if (choice == FIT_AS_MANY_AS_POSSIBLE) { m_fitAsManyCheck->setChecked(true); } else { m_photoGridCheck->setChecked(true); } } void AdvPrintCustomLayoutDlg::saveSettings() { KConfig config; KConfigGroup group = config.group(QLatin1String("PrintCreator")); int choice = PHOTO_GRID; if (m_fitAsManyCheck->isChecked()) { choice = FIT_AS_MANY_AS_POSSIBLE; } group.writeEntry(QLatin1String("Custom-choice"), choice); group.writeEntry(QLatin1String("Custom-gridSize"), QSize(m_gridRows->value(), m_gridColumns->value())); group.writeEntry(QLatin1String("Custom-photoSize"), QSize(m_photoWidth->value(), m_photoHeight->value())); group.writeEntry(QLatin1String("Custom-photoUnits"), m_photoUnits->currentIndex()); group.writeEntry(QLatin1String("Custom-autorotate"), m_autorotate->isChecked()); } } // namespace Digikam diff --git a/utilities/setup/setupicc.cpp b/utilities/setup/setupicc.cpp index 8256fb44a3..33a616dbc7 100644 --- a/utilities/setup/setupicc.cpp +++ b/utilities/setup/setupicc.cpp @@ -1,967 +1,967 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2005-11-24 * Description : Color management setup tab. * * Copyright (C) 2005-2007 by F.J. Cruz * Copyright (C) 2005-2017 by Gilles Caulier * Copyright (C) 2009-2012 by Marcel Wiesweg * * 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, 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. * * ============================================================ */ #include "setupicc.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include // Local includes #include "dlayoutbox.h" #include "squeezedcombobox.h" #include "digikam_debug.h" #include "applicationsettings.h" #include "iccprofileinfodlg.h" #include "iccprofilescombobox.h" #include "iccsettings.h" #include "iccsettingscontainer.h" #include "dactivelabel.h" #include "dfileselector.h" namespace Digikam { class SetupICC::Private { public: Private() : iccFolderLabel(0), enableColorManagement(0), defaultSRGBConvert(0), bpcAlgorithm(0), managedView(0), managedPreviews(0), defaultAskMismatch(0), defaultConvertMismatch(0), defaultAskMissing(0), defaultSRGBMissing(0), defaultWSMissing(0), defaultInputMissing(0), defaultAskRaw(0), defaultInputRaw(0), defaultGuessRaw(0), infoWorkProfiles(0), infoMonitorProfiles(0), infoInProfiles(0), infoProofProfiles(0), workspaceGB(0), mismatchGB(0), missingGB(0), rawGB(0), inputGB(0), viewGB(0), proofGB(0), iccFolderGB(0), advancedSettingsGB(0), defaultPathKU(0), renderingIntentKC(0), behaviorPanel(0), profilesPanel(0), advancedPanel(0), tab(0), dlgBtnBox(0), inProfilesKC(0), workProfilesKC(0), proofProfilesKC(0), monitorProfilesKC(0) { } QLabel* iccFolderLabel; QCheckBox* enableColorManagement; QCheckBox* defaultSRGBConvert; QCheckBox* bpcAlgorithm; QCheckBox* managedView; QCheckBox* managedPreviews; QRadioButton* defaultAskMismatch; QRadioButton* defaultConvertMismatch; QRadioButton* defaultAskMissing; QRadioButton* defaultSRGBMissing; QRadioButton* defaultWSMissing; QRadioButton* defaultInputMissing; QRadioButton* defaultAskRaw; QRadioButton* defaultInputRaw; QRadioButton* defaultGuessRaw; QPushButton* infoWorkProfiles; QPushButton* infoMonitorProfiles; QPushButton* infoInProfiles; QPushButton* infoProofProfiles; QGroupBox* workspaceGB; QGroupBox* mismatchGB; QGroupBox* missingGB; QGroupBox* rawGB; QGroupBox* inputGB; QGroupBox* viewGB; QGroupBox* proofGB; QGroupBox* iccFolderGB; QGroupBox* advancedSettingsGB; DFileSelector* defaultPathKU; IccRenderingIntentComboBox* renderingIntentKC; QWidget* behaviorPanel; QWidget* profilesPanel; QWidget* advancedPanel; QTabWidget* tab; QDialogButtonBox* dlgBtnBox; IccProfilesComboBox* inProfilesKC; IccProfilesComboBox* workProfilesKC; IccProfilesComboBox* proofProfilesKC; IccProfilesComboBox* monitorProfilesKC; }; SetupICC::SetupICC(QDialogButtonBox* const dlgBtnBox, QWidget* const parent) : QScrollArea(parent), d(new Private) { const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); d->dlgBtnBox = dlgBtnBox; d->tab = new QTabWidget(viewport()); setWidget(d->tab); setWidgetResizable(true); d->behaviorPanel = new QWidget; QVBoxLayout* const mainLayout = new QVBoxLayout(d->behaviorPanel); // -------------------------------------------------------- QWidget* const colorPolicy = new QWidget; QGridLayout* const gridHeader = new QGridLayout(colorPolicy); d->enableColorManagement = new QCheckBox(colorPolicy); d->enableColorManagement->setText(i18n("Enable Color Management")); d->enableColorManagement->setWhatsThis(i18n("
  • Checked: Color Management is enabled
  • " "
  • Unchecked: Color Management is " "disabled
")); DActiveLabel* const lcmsLogoLabel = new DActiveLabel(QUrl(QLatin1String("http://www.littlecms.com")), QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("digikam/data/logo-lcms.png")), colorPolicy); lcmsLogoLabel->setToolTip(i18n("Visit Little CMS project website")); gridHeader->addWidget(d->enableColorManagement, 0, 0, 1, 1); gridHeader->addWidget(lcmsLogoLabel, 0, 2, 1, 1); gridHeader->setColumnStretch(1, 10); gridHeader->setContentsMargins(spacing, spacing, spacing, spacing); gridHeader->setSpacing(0); // -------------------------------------------------------- d->workspaceGB = new QGroupBox(i18n("Working Color Space")); QHBoxLayout* const hboxWS = new QHBoxLayout(d->workspaceGB); QLabel* const workIcon = new QLabel; workIcon->setPixmap(QIcon::fromTheme(QLatin1String("input-tablet")).pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize))); d->workProfilesKC = new IccProfilesComboBox; d->workProfilesKC->setWhatsThis(i18n("

This is the color space all the images will be converted to when opened " "(if you choose to convert) and the profile that will be embedded when saving. " "Good and safe choices are Adobe RGB (1998) and sRGB IEC61966-2.1")); d->infoWorkProfiles = new QPushButton; d->infoWorkProfiles->setIcon(QIcon::fromTheme(QLatin1String("dialog-information"))); d->infoWorkProfiles->setWhatsThis(i18n("

You can use this button to get more detailed " "information about the selected workspace profile.

")); hboxWS->addWidget(workIcon); hboxWS->addWidget(d->workProfilesKC, 10); hboxWS->addWidget(d->infoWorkProfiles); // -------------------------------------------------------- d->mismatchGB = new QGroupBox;//(i18n("Behavior on Profile Mismatch"); QVBoxLayout* const vlayMismatch = new QVBoxLayout(d->mismatchGB); QLabel* const behaviorIcon = new QLabel; behaviorIcon->setPixmap(QIcon::fromTheme(QLatin1String("view-preview")).pixmap(32)); QLabel* const behaviorLabel = new QLabel(i18n("When the profile of an image does not match the working color space")); behaviorLabel->setWordWrap(true); QHBoxLayout* const hboxBL = new QHBoxLayout; hboxBL->addWidget(behaviorIcon); hboxBL->addWidget(behaviorLabel, 10); d->defaultAskMismatch = new QRadioButton(d->mismatchGB); d->defaultAskMismatch->setText(i18n("Ask when opening the image")); d->defaultAskMismatch->setWhatsThis(i18n("

If an image has an embedded color profile not matching the working " "space profile, digiKam will ask if you want to convert to the working space, " "keep the embedded profile or discard the embedded profile and assign " "a different one.

")); d->defaultConvertMismatch = new QRadioButton(d->mismatchGB); d->defaultConvertMismatch->setText(i18n("Convert the image to the working color space")); d->defaultConvertMismatch->setWhatsThis(i18n("

If an image has an embedded color profile not matching the working " "space profile, digiKam will convert the image's color information to " "the working color space. This changes the pixel data, but not the " "appearance of the image.

")); vlayMismatch->addLayout(hboxBL); vlayMismatch->addWidget(d->defaultAskMismatch); vlayMismatch->addWidget(d->defaultConvertMismatch); // -------------------------------------------------------- d->missingGB = new QGroupBox;//(i18n("Missing Profile Behavior")); QVBoxLayout* const vlayMissing = new QVBoxLayout(d->missingGB); QLabel* const missingIcon = new QLabel; missingIcon->setPixmap(QIcon::fromTheme(QLatin1String("paint-unknown")).pixmap(32)); QLabel* const missingLabel = new QLabel(i18n("When an image has no color profile information")); missingLabel->setWordWrap(true); QHBoxLayout* const hboxMP = new QHBoxLayout; hboxMP->addWidget(missingIcon); hboxMP->addWidget(missingLabel, 10); d->defaultAskMissing = new QRadioButton(i18n("Ask when opening the image")); d->defaultAskMissing->setWhatsThis(i18n("

If an image has no embedded color profile, " "digiKam will ask which color space shall be used to interpret the image " "and to which color space it shall be transformed for editing.

")); d->defaultSRGBMissing = new QRadioButton(i18n("Assume it is using the sRGB color space (Internet standard)")); /** * @todo d->defaultSRGBMissing->setWhatsThis( i18n("

")); */ d->defaultSRGBConvert = new QCheckBox(i18n("and convert it to the working color space")); /** * @todo d->defaultSRGBConvert->setWhatsThis( i18n("

")); */ d->defaultSRGBConvert->setChecked(true); QGridLayout* const gridRgb = new QGridLayout; gridRgb->addWidget(d->defaultSRGBMissing, 0, 0, 1, 2); gridRgb->addWidget(d->defaultSRGBConvert, 1, 1); gridRgb->setColumnMinimumWidth(0, 10); d->defaultWSMissing = new QRadioButton(i18n("Assume it is using the working color space")); /** * @todo d->defaultWSMissing->setWhatsThis( i18n("

")); */ d->defaultInputMissing = new QRadioButton(i18n("Convert it from default input color space to working space")); /** * @todo d->defaultWSMissing->setWhatsThis( i18n("

")); */ vlayMissing->addLayout(hboxMP); vlayMissing->addWidget(d->defaultAskMissing); vlayMissing->addLayout(gridRgb); vlayMissing->addWidget(d->defaultWSMissing); vlayMissing->addWidget(d->defaultInputMissing); // -------------------------------------------------------- d->rawGB = new QGroupBox;//(i18n("Raw File Behavior")); QVBoxLayout* const vlayRaw = new QVBoxLayout(d->rawGB); QLabel* const rawBehaviorIcon = new QLabel; rawBehaviorIcon->setPixmap(QIcon::fromTheme(QLatin1String("image-x-adobe-dng")).pixmap(32)); QLabel* const rawBehaviorLabel = new QLabel(i18n("When loading a RAW file with uncalibrated colors")); rawBehaviorLabel->setWordWrap(true); QHBoxLayout* const hboxRF = new QHBoxLayout; hboxRF->addWidget(rawBehaviorIcon); hboxRF->addWidget(rawBehaviorLabel, 10); d->defaultAskRaw = new QRadioButton(i18n("Ask for the input profile")); /** * @todo d->defaultAskRaw->setWhatsThis( i18n("

")); */ d->defaultGuessRaw = new QRadioButton(i18n("Automatic color correction")); /** * @todo d->defaultGuessRaw->setWhatsThis( i18n("

")); */ d->defaultInputRaw = new QRadioButton(i18n("Convert it from the default input profile")); /** * @todo d->defaultSRGBMissing->setWhatsThis( i18n("

")); */ d->defaultGuessRaw->setChecked(true); vlayRaw->addLayout(hboxRF); vlayRaw->addWidget(d->defaultAskRaw); vlayRaw->addWidget(d->defaultGuessRaw); vlayRaw->addWidget(d->defaultInputRaw); mainLayout->addWidget(colorPolicy); mainLayout->addWidget(d->workspaceGB); mainLayout->addWidget(d->mismatchGB); mainLayout->addWidget(d->missingGB); mainLayout->addWidget(d->rawGB); mainLayout->addStretch(); // -------------------------------------------------------- d->profilesPanel = new QWidget; QVBoxLayout* const vboxDisplay = new QVBoxLayout(d->profilesPanel); d->viewGB = new QGroupBox(i18n("Color Managed View")); QGridLayout* const gridView = new QGridLayout(d->viewGB); QLabel* const monitorIcon = new QLabel; monitorIcon->setPixmap(QIcon::fromTheme(QLatin1String("video-display")).pixmap(32)); QLabel* const monitorProfiles = new QLabel(i18n("Monitor profile:")); d->monitorProfilesKC = new IccProfilesComboBox; monitorProfiles->setBuddy(d->monitorProfilesKC); d->monitorProfilesKC->setWhatsThis(i18n("

Select the color profile for your monitor here.

")); d->infoMonitorProfiles = new QPushButton; d->infoMonitorProfiles->setIcon(QIcon::fromTheme(QLatin1String("dialog-information"))); d->infoMonitorProfiles->setWhatsThis(i18n("

You can use this button to get more detailed " "information about the selected monitor profile.

")); d->managedView = new QCheckBox; d->managedView->setText(i18n("Use color managed view in editor")); d->managedView->setWhatsThis(i18n("

Turn on this option if " "you want to use your Monitor Color Profile to show your pictures in " "the Image Editor window with a color correction adapted to your monitor. " "You can at any time toggle this option from the Editor window. " "Warning: This can slow down rendering of the image, depending on the speed of your computer.

")); d->managedPreviews = new QCheckBox; d->managedPreviews->setText(i18n("Use color managed view for previews and thumbnails")); /** * @todo d->managedPreview->setWhatsThis( i18n("") ); */ gridView->addWidget(monitorIcon, 0, 0); gridView->addWidget(monitorProfiles, 0, 1, 1, 2); gridView->addWidget(d->monitorProfilesKC, 1, 0, 1, 2); gridView->addWidget(d->infoMonitorProfiles, 1, 2); gridView->addWidget(d->managedView, 2, 0, 1, 3); gridView->addWidget(d->managedPreviews, 3, 0, 1, 3); gridView->setColumnStretch(1, 10); // -------------------------------------------------------- d->inputGB = new QGroupBox(i18n("Camera and Scanner")); QGridLayout* const gridIP = new QGridLayout(d->inputGB); QLabel* const inputIcon = new QLabel; inputIcon->setPixmap(QIcon::fromTheme(QLatin1String("input-tablet")).pixmap(32)); QLabel* const inputLabel = new QLabel(i18n("Default input color profile:")); d->inProfilesKC = new IccProfilesComboBox; d->inProfilesKC->setWhatsThis(i18n("

This is the default color profile for your input device " "(that is your camera - or your scanner). A camera input profile " "is recommended for correct conversion of RAW images in 16bit. " "Some of the options about loading behavior above refer to this profile.

")); d->infoInProfiles = new QPushButton; d->infoInProfiles->setIcon(QIcon::fromTheme(QLatin1String("dialog-information"))); d->infoInProfiles->setWhatsThis(i18n("

You can use this button to get more detailed " "information about the selected input profile.

")); gridIP->addWidget(inputIcon, 0, 0); gridIP->addWidget(inputLabel, 0, 1, 1, 2); gridIP->addWidget(d->inProfilesKC, 1, 0, 1, 2); gridIP->addWidget(d->infoInProfiles, 1, 2); gridIP->setColumnStretch(1, 10); // -------------------------------------------------------- d->proofGB = new QGroupBox(i18n("Printing and Proofing")); QGridLayout* const gridProof = new QGridLayout(d->proofGB); QLabel* const proofIcon = new QLabel; proofIcon->setPixmap(QIcon::fromTheme(QLatin1String("printer")).pixmap(32)); QLabel* const proofLabel = new QLabel(i18n("Output device profile:")); d->proofProfilesKC = new IccProfilesComboBox; proofLabel->setBuddy(d->proofProfilesKC); d->proofProfilesKC->setWhatsThis(i18n("

Select the profile for your output device " "(usually, your printer). This profile will be used to do a soft proof, so you will " "be able to preview how an image will be rendered via an output device.

")); d->infoProofProfiles = new QPushButton; d->infoProofProfiles->setIcon(QIcon::fromTheme(QLatin1String("dialog-information"))); d->infoProofProfiles->setWhatsThis(i18n("

You can use this button to get more detailed " "information about the selected proofing profile.

")); gridProof->addWidget(proofIcon, 0, 0); gridProof->addWidget(proofLabel, 0, 1, 1, 2); gridProof->addWidget(d->proofProfilesKC, 1, 0, 1, 2); gridProof->addWidget(d->infoProofProfiles, 1, 2); gridProof->setColumnStretch(1, 10); // -------------------------------------------------------- d->iccFolderGB = new QGroupBox(i18n("Color Profiles Repository")); QGridLayout* const gridIccFolder = new QGridLayout(d->iccFolderGB); QLabel* const iccFolderIcon = new QLabel; iccFolderIcon->setPixmap(QIcon::fromTheme(QLatin1String("folder-downloads")).pixmap(32)); d->iccFolderLabel = new QLabel(i18n("digiKam looks for ICC profiles in a number of default locations. " "You can specify an additional folder:")); d->iccFolderLabel->setWordWrap(true); d->defaultPathKU = new DFileSelector; d->iccFolderLabel->setBuddy(d->defaultPathKU); d->defaultPathKU->lineEdit()->setReadOnly(true); d->defaultPathKU->setFileDlgMode(DFileDialog::Directory); d->defaultPathKU->setWhatsThis(i18n("

digiKam searches ICC profiles in default system folders " "and ships itself a few selected profiles. " "Store all your additional color profiles in the directory set here.

")); gridIccFolder->addWidget(iccFolderIcon, 0, 0); gridIccFolder->addWidget(d->iccFolderLabel, 0, 1); gridIccFolder->addWidget(d->defaultPathKU, 1, 0, 1, 2); gridIccFolder->setColumnStretch(1, 10); vboxDisplay->addWidget(d->viewGB); vboxDisplay->addWidget(d->inputGB); vboxDisplay->addWidget(d->proofGB); vboxDisplay->addWidget(d->iccFolderGB); vboxDisplay->addStretch(1); // -------------------------------------------------------- d->advancedPanel = new QWidget; QVBoxLayout* const vboxAdvanced = new QVBoxLayout(d->advancedPanel); d->advancedSettingsGB = new QGroupBox(i18n("Advanced Settings")); QGridLayout* const gridAdvanced = new QGridLayout(d->advancedSettingsGB); d->bpcAlgorithm = new QCheckBox(d->advancedSettingsGB); d->bpcAlgorithm->setText(i18n("Use black point compensation")); d->bpcAlgorithm->setWhatsThis(i18n("

Black Point Compensation is a way to make " "adjustments between the maximum " "black levels of digital files and the black capabilities of various " "digital devices.

")); QLabel* const lablel = new QLabel(d->advancedSettingsGB); lablel->setText(i18n("Rendering Intents:")); d->renderingIntentKC = new IccRenderingIntentComboBox(d->advancedSettingsGB); gridAdvanced->addWidget(d->bpcAlgorithm, 0, 0, 1, 2); gridAdvanced->addWidget(lablel, 1, 0, 1, 1); gridAdvanced->addWidget(d->renderingIntentKC, 1, 1, 1, 1); gridAdvanced->setContentsMargins(spacing, spacing, spacing, spacing); gridAdvanced->setSpacing(0); vboxAdvanced->addWidget(d->advancedSettingsGB); vboxAdvanced->addStretch(1); // -------------------------------------------------------- d->tab->addTab(d->behaviorPanel, i18n("Behavior")); d->tab->addTab(d->profilesPanel, i18n("Profiles")); d->tab->addTab(d->advancedPanel, i18n("Advanced")); // -------------------------------------------------------- connect(d->enableColorManagement, SIGNAL(toggled(bool)), this, SLOT(slotToggledEnabled())); connect(d->infoProofProfiles, SIGNAL(clicked()), this, SLOT(slotClickedProof())); connect(d->infoInProfiles, SIGNAL(clicked()), this, SLOT(slotClickedIn())); connect(d->infoMonitorProfiles, SIGNAL(clicked()), this, SLOT(slotClickedMonitor())); connect(d->infoWorkProfiles, SIGNAL(clicked()), this, SLOT(slotClickedWork())); connect(d->defaultPathKU, SIGNAL(signalUrlSelected(QUrl)), this, SLOT(slotUrlChanged())); connect(d->defaultPathKU->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(slotUrlTextChanged())); connect(d->iccFolderLabel, SIGNAL(linkActivated(QString)), this, SLOT(slotShowDefaultSearchPaths())); connect(d->defaultAskMissing, SIGNAL(toggled(bool)), this, SLOT(slotMissingToggled(bool))); connect(d->defaultSRGBMissing, SIGNAL(toggled(bool)), this, SLOT(slotMissingToggled(bool))); connect(d->defaultWSMissing, SIGNAL(toggled(bool)), this, SLOT(slotMissingToggled(bool))); connect(d->defaultInputMissing, SIGNAL(toggled(bool)), this, SLOT(slotMissingToggled(bool))); // -------------------------------------------------------- adjustSize(); readSettings(); slotToggledEnabled(); // -------------------------------------------------------- } SetupICC::~SetupICC() { delete d; } void SetupICC::applySettings() { ICCSettingsContainer settings; settings.enableCM = d->enableColorManagement->isChecked(); if (d->defaultAskMismatch->isChecked()) { settings.defaultMismatchBehavior = ICCSettingsContainer::AskUser; } else if (d->defaultConvertMismatch->isChecked()) { settings.defaultMismatchBehavior = ICCSettingsContainer::EmbeddedToWorkspace; } if (d->defaultAskMissing->isChecked()) { settings.defaultMissingProfileBehavior = ICCSettingsContainer::AskUser; } else if (d->defaultSRGBMissing->isChecked()) { settings.defaultMissingProfileBehavior = ICCSettingsContainer::UseSRGB; if (d->defaultSRGBConvert->isChecked()) { settings.defaultMissingProfileBehavior |= ICCSettingsContainer::ConvertToWorkspace; } else { settings.defaultMissingProfileBehavior |= ICCSettingsContainer::KeepProfile; } } else if (d->defaultWSMissing->isChecked()) { settings.defaultMissingProfileBehavior = ICCSettingsContainer::UseWorkspace | ICCSettingsContainer::KeepProfile; } else if (d->defaultInputMissing->isChecked()) { settings.defaultMissingProfileBehavior = ICCSettingsContainer::InputToWorkspace; } if (d->defaultAskRaw->isChecked()) { settings.defaultUncalibratedBehavior = ICCSettingsContainer::AskUser; } else if (d->defaultInputRaw->isChecked()) { settings.defaultUncalibratedBehavior = ICCSettingsContainer::InputToWorkspace; } else if (d->defaultGuessRaw->isChecked()) { settings.defaultUncalibratedBehavior = ICCSettingsContainer::AutomaticColors | ICCSettingsContainer::ConvertToWorkspace; } settings.iccFolder = d->defaultPathKU->fileDlgPath(); settings.useBPC = d->bpcAlgorithm->isChecked(); settings.renderingIntent = d->renderingIntentKC->intent(); settings.useManagedView = d->managedView->isChecked(); settings.useManagedPreviews = d->managedPreviews->isChecked(); settings.defaultInputProfile = d->inProfilesKC->currentProfile().filePath(); settings.workspaceProfile = d->workProfilesKC->currentProfile().filePath(); settings.defaultProofProfile = d->proofProfilesKC->currentProfile().filePath(); if (!IccSettings::instance()->monitorProfileFromSystem()) { settings.monitorProfile = d->monitorProfilesKC->currentProfile().filePath(); } IccSettings::instance()->setSettings(settings); } void SetupICC::readSettings(bool restore) { ICCSettingsContainer settings = IccSettings::instance()->settings(); if (!restore) { d->enableColorManagement->setChecked(settings.enableCM); } d->bpcAlgorithm->setChecked(settings.useBPC); d->renderingIntentKC->setIntent(settings.renderingIntent); d->managedView->setChecked(settings.useManagedView); d->managedPreviews->setChecked(settings.useManagedPreviews); if (settings.defaultMismatchBehavior & ICCSettingsContainer::AskUser) { d->defaultAskMismatch->setChecked(true); } else if (settings.defaultMismatchBehavior & ICCSettingsContainer::ConvertToWorkspace) { d->defaultConvertMismatch->setChecked(true); } if (settings.defaultMissingProfileBehavior & ICCSettingsContainer::AskUser) { d->defaultAskMissing->setChecked(true); } else { if (settings.defaultMissingProfileBehavior & ICCSettingsContainer::UseSRGB) { d->defaultSRGBMissing->setChecked(true); d->defaultSRGBConvert->setChecked(settings.defaultMissingProfileBehavior & ICCSettingsContainer::ConvertToWorkspace); } else if (settings.defaultMissingProfileBehavior & ICCSettingsContainer::UseWorkspace) { d->defaultWSMissing->setChecked(true); } else if (settings.defaultMissingProfileBehavior & ICCSettingsContainer::UseDefaultInputProfile) { d->defaultInputMissing->setChecked(true); } } if (settings.defaultUncalibratedBehavior & ICCSettingsContainer::AskUser) { d->defaultAskRaw->setChecked(true); } else if (settings.defaultUncalibratedBehavior & ICCSettingsContainer::UseDefaultInputProfile) { d->defaultInputRaw->setChecked(true); } else if (settings.defaultUncalibratedBehavior & ICCSettingsContainer::AutomaticColors) { d->defaultGuessRaw->setChecked(true); } d->defaultPathKU->setFileDlgPath(settings.iccFolder); fillCombos(false); d->workProfilesKC->setCurrentProfile(settings.workspaceProfile); d->inProfilesKC->setCurrentProfile(settings.defaultInputProfile); d->proofProfilesKC->setCurrentProfile(settings.defaultProofProfile); if (IccSettings::instance()->monitorProfileFromSystem()) { d->monitorProfilesKC->clear(); d->monitorProfilesKC->setNoProfileIfEmpty(i18n("Monitor Profile From System Settings")); } else { d->monitorProfilesKC->setCurrentProfile(settings.monitorProfile); } } void SetupICC::slotUrlChanged() { IccSettings::instance()->setIccPath(d->defaultPathKU->fileDlgPath()); fillCombos(true); } void SetupICC::slotUrlTextChanged() { d->defaultPathKU->setFileDlgPath(d->defaultPathKU->fileDlgPath()); } void SetupICC::fillCombos(bool report) { if (!d->enableColorManagement->isChecked()) { return; } QList profiles = IccSettings::instance()->allProfiles(); if (profiles.isEmpty()) { if (report) { QString message = i18n("No ICC profiles files found."); QMessageBox::information(this, qApp->applicationName(), message); } qCDebug(DIGIKAM_GENERAL_LOG) << "No ICC profile files found!!!"; d->dlgBtnBox->button(QDialogButtonBox::Ok)->setEnabled(false); return; } d->workProfilesKC->replaceProfilesSqueezed(IccSettings::instance()->workspaceProfiles()); d->monitorProfilesKC->replaceProfilesSqueezed(IccSettings::instance()->displayProfiles()); d->inProfilesKC->replaceProfilesSqueezed(IccSettings::instance()->inputProfiles()); d->proofProfilesKC->replaceProfilesSqueezed(IccSettings::instance()->outputProfiles()); d->workProfilesKC->setNoProfileIfEmpty(i18n("No Profile Available")); d->monitorProfilesKC->setNoProfileIfEmpty(i18n("No Display Profile Available")); d->inProfilesKC->setNoProfileIfEmpty(i18n("No Input Profile Available")); d->proofProfilesKC->setNoProfileIfEmpty(i18n("No Output Profile Available")); if (d->monitorProfilesKC->count() == 0) { d->managedView->setEnabled(false); d->managedPreviews->setEnabled(false); } else { d->dlgBtnBox->button(QDialogButtonBox::Ok)->setEnabled(true); d->managedPreviews->setEnabled(true); } if (d->workProfilesKC->count() == 0) { // If there is no workspace ICC profiles available, // the CM is broken and cannot be used. d->dlgBtnBox->button(QDialogButtonBox::Ok)->setEnabled(false); return; } d->dlgBtnBox->button(QDialogButtonBox::Ok)->setEnabled(true); } void SetupICC::setWidgetsEnabled(bool enabled) { d->workspaceGB->setEnabled(enabled); d->mismatchGB->setEnabled(enabled); d->missingGB->setEnabled(enabled); d->rawGB->setEnabled(enabled); d->tab->setTabEnabled(1, enabled); d->tab->setTabEnabled(2, enabled); //d->profilesPanel->setEnabled(enabled); //d->advancedPanel->setEnabled(enabled); } void SetupICC::slotToggledEnabled() { bool enabled = d->enableColorManagement->isChecked(); setWidgetsEnabled(enabled); if (enabled) { readSettings(true); } else { d->dlgBtnBox->button(QDialogButtonBox::Ok)->setEnabled(true); } } void SetupICC::slotClickedWork() { IccProfile profile = d->workProfilesKC->currentProfile(); if (!profile.isNull()) { profileInfo(profile); } } void SetupICC::slotClickedIn() { IccProfile profile = d->inProfilesKC->currentProfile(); if (!profile.isNull()) { profileInfo(profile); } } void SetupICC::slotClickedMonitor() { IccProfile profile; if (IccSettings::instance()->monitorProfileFromSystem()) { profile = IccSettings::instance()->monitorProfile(); } else { profile = d->monitorProfilesKC->currentProfile(); } if (!profile.isNull()) { profileInfo(profile); } } void SetupICC::slotClickedProof() { IccProfile profile = d->proofProfilesKC->currentProfile(); if (!profile.isNull()) { profileInfo(profile); } } void SetupICC::profileInfo(const IccProfile& profile) { if (profile.isNull()) { QMessageBox::critical(this, i18n("Profile Error"), i18n("No profile is selected.")); return; } ICCProfileInfoDlg infoDlg(this, profile.filePath(), profile); infoDlg.exec(); } void SetupICC::slotMissingToggled(bool on) { if (!on) { return; } d->defaultSRGBConvert->setEnabled(d->defaultSRGBMissing->isChecked()); } void SetupICC::slotShowDefaultSearchPaths() { QStringList defaultSearchPaths = IccProfile::defaultSearchPaths(); QString existingPaths; if (defaultSearchPaths.isEmpty()) { existingPaths = i18nc("none of the paths", "none"); } else { existingPaths = defaultSearchPaths.join(QLatin1String("
  • ")); } #ifdef Q_OS_WIN QString text = i18n("On Windows, the default search paths include " "
      " "
    • %1/Windows/Spool/Drivers/Color/
    • " // For Win2K and WinXP - "
    • %2/Windows/Color/
    • " // For For Win98 and WinMe + "
    • %2/Windows/Color/
    • " // For Win98 and WinMe "
    " "On your system, currently these paths exist and are scanned:" "
      " "
    • %3
    • " "
    ", QDir::rootPath(), QDir::rootPath(), existingPaths); #elif defined (Q_OS_OSX) QString text = i18n("On Mac OS X, the default search paths include " "
      " "
    • /System/Library/ColorSync/Profiles
    • " "
    • /Library/ColorSync/Profiles
    • " "
    • ~/Library/ColorSync/Profiles
    • " "
    • /opt/local/share/color/icc
    • " "
    • /opt/digikam/share/color/icc
    • " "
    • ~/.local/share/color/icc/
    • " "
    • ~/.local/share/icc/
    • " "
    • ~/.color/icc/
    • " "
    " "On your system, currently these paths exist and are scanned:" "
      " "
    • %1
    • " "
    ", existingPaths); #else // Linux QString text = i18n("On Linux, the default search paths include " "
      " "
    • /usr/share/color/icc
    • " "
    • /usr/local/share/color/icc
    • " "
    • ~/.local/share/color/icc/
    • " "
    • ~/.local/share/icc/
    • " "
    • ~/.color/icc/
    • " "
    " "On your system, currently these paths exist and are scanned:" "
      " "
    • %1
    • " "
    ", existingPaths); #endif QWhatsThis::showText(d->iccFolderLabel->mapToGlobal(QPoint(0, 0)), text, d->iccFolderLabel); } bool SetupICC::iccRepositoryIsValid() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(QLatin1String("Color Management")); // If color management is disable, no need to check anymore. if (!group.readEntry(QLatin1String("EnableCM"), false)) { return true; } // Can at least RawEngine profiles be opened? if (IccProfile::sRGB().open()) { return true; } // To be valid, the ICC profiles repository must exist and be readable. QString extraPath = group.readEntry(QLatin1String("DefaultPath"), QString()); QFileInfo info(extraPath); if (info.isDir() && info.exists() && info.isReadable()) { return true; } QStringList paths = IccProfile::defaultSearchPaths(); return !paths.isEmpty(); } } // namespace Digikam