summaryrefslogtreecommitdiffstats
path: root/digikam/libs/threadimageio/previewtask.cpp
blob: 5c1d3949fa115c374f1aefb97a71c20aefa1da84 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
/* ============================================================
 *
 * This file is a part of digiKam project
 * http://www.digikam.org
 *
 * Date        : 2006-12-26
 * Description : Multithreaded loader for previews
 *
 * Copyright (C) 2006-2007 by Marcel Wiesweg <marcel.wiesweg@gmx.de>
 * Copyright (C) 2006-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
 *
 * 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.
 *
 * ============================================================ */

// C++ includes.

#include <cmath>

// TQt includes.

#include <tqapplication.h>
#include <tqimage.h>
#include <tqvariant.h>
#include <tqwmatrix.h>

// LibKDcraw includes.

#include <libkdcraw/kdcraw.h>

// Local includes.

#include "ddebug.h"
#include "dmetadata.h"
#include "jpegutils.h"
#include "previewloadthread.h"
#include "previewtask.h"

namespace Digikam
{

void PreviewLoadingTask::execute()
{
    if (m_loadingTasktqStatus == LoadingTaskStatusStopping)
        return;

    LoadingCache *cache = LoadingCache::cache();
    {
        LoadingCache::CacheLock lock(cache);

        // find possible cached images
        DImg *cachedImg = 0;
        TQStringList lookupKeys = m_loadingDescription.lookupCacheKeys();
        // lookupCacheKeys returns "best first". Prepend the cache key to make the list "fastest first":
        // Scaling a full version takes longer!
        lookupKeys.push_front(m_loadingDescription.cacheKey());
        for ( TQStringList::Iterator it = lookupKeys.begin(); it != lookupKeys.end(); ++it ) {
            if ( (cachedImg = cache->retrieveImage(*it)) )
                break;
        }

        if (cachedImg)
        {
            // image is found in image cache, loading is successful

            DImg img(*cachedImg);

            // rotate if needed - images are unrotated in the cache,
            // except for RAW images, which are already rotated by dcraw.
            if (m_loadingDescription.previewParameters.exifRotate)
            {
                img = img.copy();
                LoadSaveThread::exifRotate(img, m_loadingDescription.filePath);
            }

            TQApplication::postEvent(m_thread, new LoadedEvent(m_loadingDescription.filePath, img));
            return;
        }
        else
        {
            // find possible running loading process
            m_usedProcess = 0;
            for ( TQStringList::Iterator it = lookupKeys.begin(); it != lookupKeys.end(); ++it ) {
                if ( (m_usedProcess = cache->retrieveLoadingProcess(*it)) )
                {
                    break;
                }
            }
            // do not wait on other loading processes?
            //m_usedProcess = cache->retrieveLoadingProcess(m_loadingDescription.cacheKey());

            if (m_usedProcess)
            {
                // Other process is right now loading this image.
                // Add this task to the list of listeners and
                // attach this thread to the other thread, wait until loading
                // has finished.
                m_usedProcess->addListener(this);
                // break loop when either the loading has completed, or this task is being stopped
                while ( !m_usedProcess->completed() && m_loadingTasktqStatus != LoadingTaskStatusStopping )
                    lock.timedWait();
                // remove listener from process
                m_usedProcess->removeListener(this);
                // wake up the process which is waiting until all listeners have removed themselves
                lock.wakeAll();
                // set to 0, as checked in settqStatus
                m_usedProcess = 0;
                return;
            }
            else
            {
                // Neither in cache, nor currently loading in different thread.
                // Load it here and now, add this LoadingProcess to cache list.
                cache->addLoadingProcess(this);
                // Add this to the list of listeners
                addListener(this);
                // for use in settqStatus
                m_usedProcess = this;
                // Notify other processes that we are now loading this image.
                // They might be interested - see notifyNewLoadingProcess below
                cache->notifyNewLoadingProcess(this, m_loadingDescription);
            }
        }
    }

    // load image
    int  size = m_loadingDescription.previewParameters.size;

    DImg img;
    TQImage qimage;
    bool fromEmbeddedPreview = false;

    // -- Get the image preview --------------------------------

    // First the TQImage-dependent loading methods
    // Trying to load with dcraw: RAW files.
    if (KDcrawIface::KDcraw::loadEmbeddedPreview(qimage, m_loadingDescription.filePath))
        fromEmbeddedPreview = true;

    if (qimage.isNull())
    {
        //TODO: Use DImg based loader instead?
        KDcrawIface::KDcraw::loadHalfPreview(qimage, m_loadingDescription.filePath);
    }

    // Try to extract Exif/Iptc preview.
    if (qimage.isNull())
    {
        loadImagePreview(qimage, m_loadingDescription.filePath);
    }

    if (!qimage.isNull())
    {
        // convert from TQImage
        img = DImg(qimage);
        // mark as embedded preview (for exif rotation)
        if (fromEmbeddedPreview)
            img.setAttribute("fromRawEmbeddedPreview", true);
        // free memory
        qimage = TQImage();
    }

    // DImg-dependent loading methods
    if (img.isNull())
    {
        // Set a hint to try to load a JPEG with the fast scale-before-decoding method
        img.setAttribute("jpegScaledLoadingSize", size);
        img.load(m_loadingDescription.filePath, this, m_loadingDescription.rawDecodingSettings);
    }

    if (img.isNull())
    {
        DWarning() << "Cannot extract preview for " << m_loadingDescription.filePath << endl;
    }

    img.convertToEightBit();

    // Reduce size of image:
    // - only scale down if size is considerably larger
    // - only scale down, do not scale up
    TQSize scaledSize = img.size();
    if (needToScale(scaledSize, size))
    {
        scaledSize.tqscale(size, size, TQSize::ScaleMin);
        img = img.smoothScale(scaledSize.width(), scaledSize.height());
    }

    // Scale if hinted, Store previews rotated in the cache (?)
    if (m_loadingDescription.previewParameters.exifRotate)
        LoadSaveThread::exifRotate(img, m_loadingDescription.filePath);

    {
        LoadingCache::CacheLock lock(cache);
        // put (valid) image into cache of loaded images
        if (!img.isNull())
            cache->putImage(m_loadingDescription.cacheKey(), new DImg(img), m_loadingDescription.filePath);
        // remove this from the list of loading processes in cache
        cache->removeLoadingProcess(this);
    }

    // following the golden rule to avoid deadlocks, do this when CacheLock is not held
    m_thread->taskHasFinished();

    {
        LoadingCache::CacheLock lock(cache);
        // indicate that loading has finished so that listeners can stop waiting
        m_completed = true;

        // dispatch image to all listeners, including this
        for (LoadingProcessListener *l = m_listeners.first(); l; l = m_listeners.next())
        {
            TQApplication::postEvent(l->eventReceiver(), new LoadedEvent(m_loadingDescription, img));
        }

        // remove myself from list of listeners
        removeListener(this);
        // wake all listeners waiting on cache condVar, so that they remove themselves
        lock.wakeAll();
        // wait until all listeners have removed themselves
        while (m_listeners.count() != 0)
            lock.timedWait();
        // set to 0, as checked in settqStatus
        m_usedProcess = 0;
    }
}

bool PreviewLoadingTask::needToScale(const TQSize &imageSize, int previewSize)
{
    int maxSize = imageSize.width() > imageSize.height() ? imageSize.width() : imageSize.height();
    int acceptableUpperSize = lround(1.25 * (double)previewSize);
    return  maxSize >= acceptableUpperSize;
}

// -- Exif/IPTC preview extraction using Exiv2 --------------------------------------------------------

bool PreviewLoadingTask::loadImagePreview(TQImage& image, const TQString& path)
{
    DMetadata metadata(path);
    if (metadata.getImagePreview(image))
    {
        DDebug() << "Use Exif/Iptc preview extraction. Size of image: "
                  << image.width() << "x" << image.height() << endl;
        return true;
    }

    return false;
}

} // namespace Digikam