~ubuntu-branches/ubuntu/utopic/kde-workspace/utopic-proposed

« back to all changes in this revision

Viewing changes to kdm/kcm/background/bgrender.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Michał Zając
  • Date: 2011-07-09 08:31:15 UTC
  • Revision ID: james.westby@ubuntu.com-20110709083115-ohyxn6z93mily9fc
Tags: upstream-4.6.90
Import upstream version 4.6.90

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* vi: ts=8 sts=4 sw=4
 
2
 * kate: space-indent on; tab-width 8; indent-width 4; indent-mode cstyle;
 
3
 *
 
4
 * This file is part of the KDE project, module kdesktop.
 
5
 * Copyright (C) 1999 Geert Jansen <g.t.jansen@stud.tue.nl>
 
6
 *
 
7
 * You can Freely distribute this program under the GNU Library General
 
8
 * Public License. See the file "COPYING.LIB" for the exact licensing terms.
 
9
 */
 
10
#include "bgrender.h"
 
11
 
 
12
#include <fixx11h.h>
 
13
#include <config-workspace.h>
 
14
 
 
15
#include <time.h>
 
16
#include <stdlib.h>
 
17
#include <utime.h>
 
18
 
 
19
#include <QTimer>
 
20
#include <QPainter>
 
21
#include <QImage>
 
22
#include <QFileInfo>
 
23
#include <QDir>
 
24
#include <QDesktopWidget>
 
25
#include <QPaintEngine>
 
26
#include <QHash>
 
27
#include <QPixmap>
 
28
#include <QSvgRenderer>
 
29
 
 
30
#include <kapplication.h>
 
31
#include <kconfig.h>
 
32
#include <kdebug.h>
 
33
#include <kstandarddirs.h>
 
34
#include <qimageblitz.h>
 
35
#include <ktemporaryfile.h>
 
36
#include <kcursor.h>
 
37
#include <kfilemetainfo.h>
 
38
#include <kconfiggroup.h>
 
39
#include <kmacroexpander.h>
 
40
 
 
41
#include "bgdefaults.h"
 
42
 
 
43
#include <X11/Xlib.h>
 
44
 
 
45
#include <QX11Info>
 
46
 
 
47
/**** KBackgroundRenderer ****/
 
48
 
 
49
 
 
50
KBackgroundRenderer::KBackgroundRenderer(int screen, bool drawBackgroundPerScreen, const KSharedConfigPtr &config)
 
51
    : KBackgroundSettings(screen, drawBackgroundPerScreen, config)
 
52
{
 
53
    m_State = 0;
 
54
    m_isBusyCursor = false;
 
55
    m_enableBusyCursor = false;
 
56
    m_pDirs = KGlobal::dirs();
 
57
    m_rSize = m_Size = drawBackgroundPerScreen ?
 
58
            QApplication::desktop()->screenGeometry(screen).size() : QApplication::desktop()->size();
 
59
    m_pProc = 0L;
 
60
    m_Tempfile = 0L;
 
61
    m_bPreview = false;
 
62
    m_Cached = false;
 
63
    m_TilingEnabled = false;
 
64
 
 
65
    m_pTimer = new QTimer(this);
 
66
    m_pTimer->setSingleShot(true);
 
67
    connect(m_pTimer, SIGNAL(timeout()), SLOT(render()));
 
68
}
 
69
 
 
70
 
 
71
KBackgroundRenderer::~KBackgroundRenderer()
 
72
{
 
73
    cleanup();
 
74
    delete m_Tempfile;
 
75
    m_Tempfile = 0;
 
76
}
 
77
 
 
78
 
 
79
void KBackgroundRenderer::setSize(const QSize &size)
 
80
{
 
81
    m_rSize = m_Size = size;
 
82
}
 
83
 
 
84
/*
 
85
 * Re-configure because the desktop has been resized.
 
86
 */
 
87
void KBackgroundRenderer::desktopResized()
 
88
{
 
89
    m_State = 0;
 
90
    m_rSize = drawBackgroundPerScreen() ?
 
91
            QApplication::desktop()->screenGeometry(screen()).size() : QApplication::desktop()->size();
 
92
    if (!m_bPreview)
 
93
        m_Size = m_rSize;
 
94
}
 
95
 
 
96
 
 
97
void KBackgroundRenderer::tile(QImage &dest, const QRect &_rect, const QImage &src)
 
98
{
 
99
    QRect rect = _rect;
 
100
    rect &= dest.rect();
 
101
 
 
102
    int x, y;
 
103
    int h = rect.height(), w = rect.width();
 
104
    int offx = rect.x(), offy = rect.y();
 
105
    int sw = src.width(), sh = src.height();
 
106
 
 
107
    for (y = offy; y < offy + h; y++)
 
108
        for (x = offx; x < offx + w; x++)
 
109
            dest.setPixel(x, y, src.pixel(x % sw, y % sh));
 
110
}
 
111
 
 
112
 
 
113
/*
 
114
 * Build a command line to run the program.
 
115
 */
 
116
 
 
117
QString KBackgroundRenderer::buildCommand()
 
118
{
 
119
    QString cmd;
 
120
    if (m_bPreview)
 
121
        cmd = previewCommand();
 
122
    else
 
123
        cmd = command();
 
124
 
 
125
    if (cmd.isEmpty())
 
126
        return QString();
 
127
 
 
128
    QHash<QChar, QString> map;
 
129
    map.insert('f', m_Tempfile->fileName());
 
130
    map.insert('x', QString::number(m_Size.width()));
 
131
    map.insert('y', QString::number(m_Size.height()));
 
132
    return KMacroExpander::expandMacrosShellQuote(cmd, map);
 
133
}
 
134
 
 
135
 
 
136
/*
 
137
 * Create a background tile. If the background mode is `Program',
 
138
 * this is asynchronous.
 
139
 */
 
140
int KBackgroundRenderer::doBackground(bool quit)
 
141
{
 
142
    if (m_State & BackgroundDone)
 
143
        return Done;
 
144
    int bgmode = backgroundMode();
 
145
 
 
146
    if (!enabled())
 
147
        bgmode = Flat;
 
148
 
 
149
    if (quit) {
 
150
        if (bgmode == Program && m_pProc)
 
151
            m_pProc->terminate();
 
152
        return Done;
 
153
    }
 
154
 
 
155
    int retval = Done;
 
156
    QString file;
 
157
 
 
158
    static unsigned int tileWidth = 0;
 
159
    static unsigned int tileHeight = 0;
 
160
    if (tileWidth == 0) {
 
161
        int tile_val = QPixmap::defaultDepth() >= 24 ? 1 : 2;
 
162
        // some dithering may be needed even with bpb==15/16, so don't use tileWidth==1
 
163
        // for them
 
164
        // with tileWidth>2, repainting the desktop causes nasty effect (XFree86 4.1.0)
 
165
        if (XQueryBestTile(QX11Info::display(), QX11Info::appRootWindow(), tile_val, tile_val,
 
166
                           &tileWidth, &tileHeight) != Success)
 
167
            tileWidth = tileHeight = tile_val; // some defaults
 
168
    }
 
169
    switch (bgmode) {
 
170
 
 
171
    case Flat:
 
172
        // this can be tiled correctly without problems
 
173
        m_Background = QImage(tileWidth, tileHeight, QImage::Format_RGB32);
 
174
        m_Background.fill(colorA().rgb());
 
175
        break;
 
176
 
 
177
    case Pattern: {
 
178
        if (pattern().isEmpty())
 
179
            break;
 
180
        file = m_pDirs->findResource("dtop_pattern", pattern());
 
181
        if (file.isEmpty())
 
182
            break;
 
183
 
 
184
        m_Background.load(file);
 
185
        if (m_Background.isNull())
 
186
            break;
 
187
        int w = m_Background.width();
 
188
        int h = m_Background.height();
 
189
        if ((w > m_Size.width()) || (h > m_Size.height())) {
 
190
            w = qMin(w, m_Size.width());
 
191
            h = qMin(h, m_Size.height());
 
192
            m_Background = m_Background.copy(0, 0, w, h);
 
193
        }
 
194
        Blitz::flatten(m_Background, colorA(), colorB());
 
195
        break;
 
196
    }
 
197
    case Program:
 
198
        if (m_State & BackgroundStarted)
 
199
            break;
 
200
        m_State |= BackgroundStarted;
 
201
        createTempFile();
 
202
 
 
203
        file = buildCommand();
 
204
        if (file.isEmpty())
 
205
            break;
 
206
 
 
207
        delete m_pProc;
 
208
        m_pProc = new KProcess;
 
209
        m_pProc->setShellCommand(file);
 
210
        connect(m_pProc,
 
211
                SIGNAL(finished(int, QProcess::ExitStatus)),
 
212
                SLOT(slotBackgroundDone(int, QProcess::ExitStatus)));
 
213
        m_pProc->start();
 
214
        retval = Wait;
 
215
        break;
 
216
 
 
217
    case HorizontalGradient: {
 
218
        QSize size = m_Size;
 
219
        // on <16bpp displays the gradient sucks when tiled because of dithering
 
220
        if (canTile())
 
221
            size.setHeight(tileHeight);
 
222
        m_Background = Blitz::gradient(size, colorA(), colorB(),
 
223
                                       Blitz::HorizontalGradient);
 
224
        break;
 
225
    }
 
226
    case VerticalGradient: {
 
227
        QSize size = m_Size;
 
228
        // on <16bpp displays the gradient sucks when tiled because of dithering
 
229
        if (canTile())
 
230
            size.setWidth(tileWidth);
 
231
        m_Background = Blitz::gradient(size, colorA(), colorB(),
 
232
                                       Blitz::VerticalGradient);
 
233
        break;
 
234
    }
 
235
    case PyramidGradient:
 
236
        m_Background = Blitz::gradient(m_Size, colorA(), colorB(),
 
237
                                       Blitz::PyramidGradient);
 
238
        break;
 
239
 
 
240
    case PipeCrossGradient:
 
241
        m_Background = Blitz::gradient(m_Size, colorA(), colorB(),
 
242
                                       Blitz::PipeCrossGradient);
 
243
        break;
 
244
 
 
245
    case EllipticGradient:
 
246
        m_Background = Blitz::gradient(m_Size, colorA(), colorB(),
 
247
                                       Blitz::EllipticGradient);
 
248
        break;
 
249
    }
 
250
 
 
251
    if (retval == Done)
 
252
        m_State |= BackgroundDone;
 
253
 
 
254
    return retval;
 
255
}
 
256
 
 
257
 
 
258
int KBackgroundRenderer::doWallpaper(bool quit)
 
259
{
 
260
    if (m_State & WallpaperDone)
 
261
        return Done;
 
262
 
 
263
    if (quit)
 
264
        // currently no asynch. wallpapers
 
265
        return Done;
 
266
 
 
267
    int wpmode = enabled() ? wallpaperMode() : NoWallpaper;
 
268
 
 
269
    m_Wallpaper = QImage();
 
270
    if (wpmode != NoWallpaper) {
 
271
wp_load:
 
272
        if (currentWallpaper().isEmpty()) {
 
273
            wpmode = NoWallpaper;
 
274
            goto wp_out;
 
275
        }
 
276
        QString file = m_pDirs->findResource("wallpaper", currentWallpaper());
 
277
        if (file.isEmpty()) {
 
278
            wpmode = NoWallpaper;
 
279
            goto wp_out;
 
280
        }
 
281
 
 
282
        // _Don't_ use KMimeType, as it relies on ksycoca which we really
 
283
        // don't want in krootimage (kdm context).
 
284
        //if (KMimeType::findByPath(file)->is("image/svg+xml")) {
 
285
        if (file.endsWith(".svg") || file.endsWith(".svgz")) {
 
286
 
 
287
            // Special stuff for SVG icons
 
288
 
 
289
            //FIXME
 
290
            //ksvgiconloader doesn't seem to let us find out the
 
291
            //ratio of width to height so for the most part we just
 
292
            //assume it's a square
 
293
            int svgWidth;
 
294
            int svgHeight;
 
295
            switch (wpmode) {
 
296
            case Centred:
 
297
            case CentredAutoFit:
 
298
                svgHeight = (int)(m_Size.height() * 0.8);
 
299
                svgWidth = svgHeight;
 
300
                break;
 
301
            case Tiled:
 
302
            case CenterTiled:
 
303
                svgHeight = (int)(m_Size.height() * 0.5);
 
304
                svgWidth = svgHeight;
 
305
                break;
 
306
            case Scaled:
 
307
                svgHeight = m_Size.height();
 
308
                svgWidth = m_Size.width();
 
309
                break;
 
310
            case CentredMaxpect:
 
311
            case ScaleAndCrop:
 
312
            case TiledMaxpect:
 
313
                svgHeight = m_Size.height();
 
314
                svgWidth = svgHeight;
 
315
                break;
 
316
            case NoWallpaper:
 
317
            default:
 
318
                kWarning() << "unknown diagram type" ;
 
319
                svgHeight = m_Size.height();
 
320
                svgWidth = svgHeight;
 
321
                break;
 
322
            }
 
323
            //FIXME hack due to strangeness with
 
324
            //background control modules
 
325
            if (svgHeight < 200) {
 
326
                svgHeight *= 6;
 
327
                svgWidth *= 6;
 
328
            }
 
329
 
 
330
            QSvgRenderer renderer(file);
 
331
            if (renderer.isValid()) {
 
332
                m_Wallpaper = QImage(svgWidth, svgHeight, QImage::Format_ARGB32_Premultiplied);
 
333
                m_Wallpaper.fill(0);
 
334
                QPainter p(&m_Wallpaper);
 
335
                renderer.render(&p);
 
336
            }
 
337
        } else {
 
338
            m_Wallpaper.load(file);
 
339
        }
 
340
        if (m_Wallpaper.isNull()) {
 
341
            kWarning() << "failed to load wallpaper " << file ;
 
342
            if (discardCurrentWallpaper())
 
343
                goto wp_load;
 
344
            wpmode = NoWallpaper;
 
345
            goto wp_out;
 
346
        }
 
347
        m_Wallpaper = m_Wallpaper.convertToFormat(QImage::Format_ARGB32_Premultiplied, Qt::DiffuseAlphaDither);
 
348
 
 
349
        // If we're previewing, scale the wallpaper down to make the preview
 
350
        // look more like the real desktop.
 
351
        if (m_bPreview) {
 
352
            int xs = m_Wallpaper.width() * m_Size.width() / m_rSize.width();
 
353
            int ys = m_Wallpaper.height() * m_Size.height() / m_rSize.height();
 
354
            if ((xs < 1) || (ys < 1)) {
 
355
                xs = ys = 1;
 
356
            }
 
357
            if (m_WallpaperRect.size() != QSize(xs, ys))
 
358
                m_Wallpaper = m_Wallpaper.scaled(xs, ys, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
 
359
        }
 
360
 
 
361
#if 0
 
362
        // HACK: Use KFileMetaInfo only when we have a KApplication
 
363
        // KFileMetaInfo needs ksycoca and so on, but this code is
 
364
        // used also in krootimage (which in turn is used by kdm).
 
365
        if (kapp) {
 
366
            KFileMetaInfo metaInfo(file);
 
367
            if (metaInfo.isValid() && metaInfo.item("Orientation").isValid()) {
 
368
                switch (metaInfo.item("Orientation").value().toInt()) {
 
369
                case 2:
 
370
                    // Flipped horizontally
 
371
                    m_Wallpaper.mirrored(true, false);
 
372
                    break;
 
373
                case 3:
 
374
                    // Rotated 180 degrees
 
375
                    m_Wallpaper = m_Wallpaper.transformed(QMatrix().rotate(180));
 
376
                    break;
 
377
                case 4:
 
378
                    // Flipped vertically
 
379
                    m_Wallpaper = m_Wallpaper.mirrored(false, true);
 
380
                    break;
 
381
                case 5:
 
382
                    // Rotated 90 degrees & flipped horizontally
 
383
                    m_Wallpaper = m_Wallpaper.transformed(QMatrix().rotate(90)).mirrored(true, false);
 
384
                    break;
 
385
                case 6:
 
386
                    // Rotated 90 degrees
 
387
                    m_Wallpaper = m_Wallpaper.transformed(QMatrix().rotate(90));
 
388
                    break;
 
389
                case 7:
 
390
                    // Rotated 90 degrees & flipped vertically
 
391
                    m_Wallpaper = m_Wallpaper.transformed(QMatrix().rotate(90)).mirrored(false, true);
 
392
                    break;
 
393
                case 8:
 
394
                    // Rotated 270 degrees
 
395
                    m_Wallpaper = m_Wallpaper.transformed(QMatrix().rotate(270));
 
396
                    break;
 
397
                case 1:
 
398
                default:
 
399
                    // Normal or invalid orientation
 
400
                    break;
 
401
                }
 
402
            }
 
403
        }
 
404
#endif
 
405
    }
 
406
wp_out:
 
407
 
 
408
    if (m_Background.isNull()) {
 
409
        m_Background = QImage(8, 8, QImage::Format_RGB32);
 
410
        m_Background.fill(colorA().rgb());
 
411
    }
 
412
 
 
413
    int retval = Done;
 
414
 
 
415
    // desktop width/height
 
416
    int w = m_Size.width();
 
417
    int h = m_Size.height();
 
418
 
 
419
    // wallpaper width/height
 
420
    int ww = m_Wallpaper.width();
 
421
    int wh = m_Wallpaper.height();
 
422
 
 
423
    // to be filled destination rectangle; may exceed desktop!
 
424
    m_WallpaperRect = QRect();
 
425
 
 
426
    switch (wpmode) {
 
427
    case NoWallpaper:
 
428
        break;
 
429
    case Centred:
 
430
        m_WallpaperRect.setRect((w - ww) / 2, (h - wh) / 2, ww, wh);
 
431
        break;
 
432
    case Tiled:
 
433
        m_WallpaperRect.setRect(0, 0, w, h);
 
434
        break;
 
435
    case CenterTiled:
 
436
        m_WallpaperRect.setCoords(-ww + ((w - ww) / 2) % ww, -wh + ((h - wh) / 2) % wh, w - 1, h - 1);
 
437
        break;
 
438
    case Scaled:
 
439
        ww = w;
 
440
        wh = h;
 
441
        if (m_WallpaperRect.size() != QSize(w, h))
 
442
            m_Wallpaper = m_Wallpaper.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
 
443
        m_WallpaperRect.setRect(0, 0, w, h);
 
444
        break;
 
445
    case CentredAutoFit:
 
446
        if (ww <= w && wh <= h) {
 
447
            m_WallpaperRect.setRect((w - ww) / 2, (h - wh) / 2, ww, wh); // like Centred
 
448
            break;
 
449
        }
 
450
        // fall through
 
451
    case CentredMaxpect: {
 
452
        double sx = (double) w / ww;
 
453
        double sy = (double) h / wh;
 
454
        if (sx > sy) {
 
455
            ww = (int)(sy * ww);
 
456
            wh = h;
 
457
        } else {
 
458
            wh = (int)(sx * wh);
 
459
            ww = w;
 
460
        }
 
461
        if (m_WallpaperRect.size() != QSize(ww, wh))
 
462
            m_Wallpaper = m_Wallpaper.scaled(ww, wh, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
 
463
        m_WallpaperRect.setRect((w - ww) / 2, (h - wh) / 2, ww, wh);
 
464
        break;
 
465
    }
 
466
    case TiledMaxpect: {
 
467
        double sx = (double) w / ww;
 
468
        double sy = (double) h / wh;
 
469
        if (sx > sy) {
 
470
            ww = (int)(sy * ww);
 
471
            wh = h;
 
472
        } else {
 
473
            wh = (int)(sx * wh);
 
474
            ww = w;
 
475
        }
 
476
        if (m_WallpaperRect.size() != QSize(ww, wh))
 
477
            m_Wallpaper = m_Wallpaper.scaled(ww, wh, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
 
478
        m_WallpaperRect.setRect(0, 0, w, h);
 
479
        break;
 
480
    }
 
481
    case ScaleAndCrop: {
 
482
        double sx = (double) w / ww;
 
483
        double sy = (double) h / wh;
 
484
        if (sx > sy) {
 
485
            //Case 1: x needs bigger scaling. Lets increase x and leave part of y offscreen
 
486
            ww = w;
 
487
            wh = (int)(sx * wh);
 
488
        } else {
 
489
            //Case 2: y needs bigger scaling. Lets increase y and leave part of x offscreen
 
490
            wh = h;
 
491
            ww = (int)(sy * ww);
 
492
        }
 
493
        if (m_WallpaperRect.size() != QSize(ww, wh))
 
494
            m_Wallpaper = m_Wallpaper.scaled(ww, wh, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
 
495
        m_WallpaperRect.setRect((w - ww) / 2, (h - wh) / 2, w, h);
 
496
        break;
 
497
    }
 
498
    }
 
499
 
 
500
    wallpaperBlend();
 
501
 
 
502
    if (retval == Done)
 
503
        m_State |= WallpaperDone;
 
504
 
 
505
    return retval;
 
506
}
 
507
 
 
508
bool KBackgroundRenderer::canTile() const
 
509
{
 
510
    return m_TilingEnabled && optimize();
 
511
}
 
512
 
 
513
void KBackgroundRenderer::wallpaperBlend()
 
514
{
 
515
    if (!enabled() || wallpaperMode() == NoWallpaper
 
516
            || (blendMode() == NoBlending &&
 
517
                (QApplication::desktop()->paintEngine()->hasFeature(QPaintEngine::Antialiasing)
 
518
                 || !m_Wallpaper.hasAlphaChannel()))) {
 
519
        fastWallpaperBlend();
 
520
    } else {
 
521
        fullWallpaperBlend();
 
522
    }
 
523
}
 
524
 
 
525
// works only for NoBlending and no alpha in wallpaper
 
526
// but is much faster than QImage fidling
 
527
void KBackgroundRenderer::fastWallpaperBlend()
 
528
{
 
529
    m_Image = QImage();
 
530
    // copy background to m_pPixmap
 
531
    if (!enabled() || (wallpaperMode() == NoWallpaper && canTile())) {
 
532
        // if there's no wallpaper, no need to tile the pixmap to the size of desktop, as X does
 
533
        // that automatically and using a smaller pixmap should save some memory
 
534
        m_Pixmap = QPixmap::fromImage(m_Background);
 
535
        return;
 
536
    } else if (wallpaperMode() == Tiled && !m_Wallpaper.hasAlphaChannel() && canTile() && !m_bPreview) {
 
537
        // tiles will be tiled by X automatically
 
538
        m_Pixmap = QPixmap::fromImage(m_Wallpaper);
 
539
        return;
 
540
    } else if (m_WallpaperRect.contains(QRect(QPoint(0, 0), m_Size))
 
541
               && !m_Wallpaper.hasAlphaChannel()) // wallpaper covers all and no blending
 
542
        m_Pixmap = QPixmap(m_Size);
 
543
    else if (m_Background.size() == m_Size)
 
544
        m_Pixmap = QPixmap::fromImage(m_Background);
 
545
    else {
 
546
        m_Pixmap = QPixmap(m_Size);
 
547
        QPainter p(&m_Pixmap);
 
548
        QPixmap pm = QPixmap::fromImage(m_Background);
 
549
        p.drawTiledPixmap(0, 0, m_Size.width(), m_Size.height(), pm);
 
550
    }
 
551
 
 
552
    // paint/alpha-blend wallpaper to destination rectangle of m_pPixmap
 
553
    if (m_WallpaperRect.isValid()) {
 
554
        QPixmap wp_pixmap = QPixmap::fromImage(m_Wallpaper);
 
555
        QPainter pa(&m_Pixmap);
 
556
        int ww = m_Wallpaper.width();
 
557
        int wh = m_Wallpaper.height();
 
558
        for (int y = m_WallpaperRect.top(); y < m_WallpaperRect.bottom(); y += wh) {
 
559
            for (int x = m_WallpaperRect.left(); x < m_WallpaperRect.right(); x += ww) {
 
560
                pa.drawPixmap(x, y, wp_pixmap);
 
561
            }
 
562
        }
 
563
    }
 
564
}
 
565
 
 
566
 
 
567
void KBackgroundRenderer::fullWallpaperBlend()
 
568
{
 
569
    m_Pixmap = QPixmap();
 
570
 
 
571
    // desktop width/height
 
572
    int w = m_Size.width();
 
573
    int h = m_Size.height();
 
574
 
 
575
    // copy background to m_pImage
 
576
    if (m_Background.size() == m_Size) {
 
577
        m_Image = m_Background.copy();
 
578
 
 
579
        if (m_Image.depth() < 32) {
 
580
            m_Image = m_Image.convertToFormat(QImage::Format_ARGB32_Premultiplied, Qt::DiffuseAlphaDither);
 
581
        }
 
582
    } else {
 
583
        m_Image = QImage(w, h, QImage::Format_RGB32);
 
584
        tile(m_Image, QRect(0, 0, w, h), m_Background);
 
585
    }
 
586
 
 
587
    // blend wallpaper to destination rectangle of m_pImage
 
588
    if (m_WallpaperRect.isValid()) {
 
589
        int blendFactor = 100;
 
590
        if (blendMode() == FlatBlending)
 
591
            blendFactor = (blendBalance() + 200) / 4;
 
592
        int ww = m_Wallpaper.width();
 
593
        int wh = m_Wallpaper.height();
 
594
        for (int y = m_WallpaperRect.top(); y < m_WallpaperRect.bottom(); y += wh) {
 
595
            for (int x = m_WallpaperRect.left(); x < m_WallpaperRect.right(); x += ww) {
 
596
                blend(m_Image, QRect(x, y, ww, wh), m_Wallpaper,
 
597
                      QPoint(-qMin(x, 0), -qMin(y, 0)), blendFactor);
 
598
            }
 
599
        }
 
600
    }
 
601
 
 
602
 
 
603
    // blend whole desktop
 
604
    if (wallpaperMode() != NoWallpaper) {
 
605
        int bal = blendBalance();
 
606
 
 
607
        switch (blendMode()) {
 
608
            /* TODO disabled for now
 
609
                  case HorizontalBlending:
 
610
                    Blitz::blend(m_Image, m_Background,
 
611
                        Blitz::HorizontalGradient,
 
612
                               bal, 100);
 
613
                    break;
 
614
 
 
615
                  case VerticalBlending:
 
616
                    Blitz::blend(m_Image, m_Background,
 
617
                        Blitz::VerticalGradient,
 
618
                               100, bal);
 
619
                    break;
 
620
 
 
621
                  case PyramidBlending:
 
622
                    Blitz::blend(m_Image, m_Background,
 
623
                        Blitz::PyramidGradient,
 
624
                               bal, bal);
 
625
                    break;
 
626
 
 
627
                  case PipeCrossBlending:
 
628
                    Blitz::blend(m_Image, m_Background,
 
629
                        Blitz::PipeCrossGradient,
 
630
                               bal, bal);
 
631
                    break;
 
632
 
 
633
                  case EllipticBlending:
 
634
                    Blitz::blend(m_Image, m_Background,
 
635
                        Blitz::EllipticGradient,
 
636
                               bal, bal);
 
637
                    break;
 
638
            */
 
639
 
 
640
        case IntensityBlending:
 
641
            Blitz::modulate(m_Image, m_Background, reverseBlending(),
 
642
                            Blitz::Intensity, bal, Blitz::All);
 
643
            break;
 
644
 
 
645
        case SaturateBlending:
 
646
            Blitz::modulate(m_Image, m_Background, reverseBlending(),
 
647
                            Blitz::Saturation, bal, Blitz::Brightness);
 
648
            break;
 
649
 
 
650
        case ContrastBlending:
 
651
            Blitz::modulate(m_Image, m_Background, reverseBlending(),
 
652
                            Blitz::Contrast, bal, Blitz::All);
 
653
            break;
 
654
 
 
655
        case HueShiftBlending:
 
656
            Blitz::modulate(m_Image, m_Background, reverseBlending(),
 
657
                            Blitz::HueShift, bal, Blitz::Brightness);
 
658
            break;
 
659
 
 
660
        case FlatBlending:
 
661
            // Already handled
 
662
            break;
 
663
        }
 
664
    }
 
665
}
 
666
 
 
667
/* Alpha blend an area from <src> with offset <soffs> to rectangle <dr> of <dst>
 
668
 * Default offset is QPoint(0, 0).
 
669
 * blendfactor = [0, 100%]
 
670
 */
 
671
void KBackgroundRenderer::blend(QImage &dst, const QRect &_dr, const QImage &src, const QPoint &soffs, int blendFactor)
 
672
{
 
673
    QRect dr = _dr;
 
674
    int x, y, a;
 
675
    dr &= dst.rect();
 
676
 
 
677
    for (y = 0; y < dr.height(); y++) {
 
678
        if (dst.scanLine(dr.y() + y) && src.scanLine(soffs.y() + y)) {
 
679
            QRgb *b;
 
680
            const QRgb *d;
 
681
            for (x = 0; x < dr.width(); x++) {
 
682
                b = reinterpret_cast<QRgb *>(dst.scanLine(dr.y() + y)
 
683
                                             + (dr.x() + x) * sizeof(QRgb));
 
684
                d = reinterpret_cast<const QRgb *>(src.scanLine(soffs.y() + y)
 
685
                                                   + (soffs.x() + x) * sizeof(QRgb));
 
686
                a = (qAlpha(*d) * blendFactor) / 100;
 
687
                *b = qRgb(qRed(*b) - (((qRed(*b) - qRed(*d)) * a) >> 8),
 
688
                          qGreen(*b) - (((qGreen(*b) - qGreen(*d)) * a) >> 8),
 
689
                          qBlue(*b) - (((qBlue(*b) - qBlue(*d)) * a) >> 8));
 
690
            }
 
691
        }
 
692
    }
 
693
}
 
694
 
 
695
 
 
696
 
 
697
void KBackgroundRenderer::slotBackgroundDone(int exitCode, QProcess::ExitStatus exitStatus)
 
698
{
 
699
    m_State |= BackgroundDone;
 
700
 
 
701
    if (exitStatus == QProcess::NormalExit && !exitCode) {
 
702
        m_Background.load(m_Tempfile->fileName());
 
703
        m_State |= BackgroundDone;
 
704
    }
 
705
 
 
706
    delete m_Tempfile;
 
707
    m_Tempfile = 0;
 
708
    m_pTimer->start(0);
 
709
    setBusyCursor(false);
 
710
}
 
711
 
 
712
 
 
713
 
 
714
/*
 
715
 * Starts the rendering process.
 
716
 */
 
717
void KBackgroundRenderer::start(bool enableBusyCursor)
 
718
{
 
719
    m_enableBusyCursor = enableBusyCursor;
 
720
    setBusyCursor(true);
 
721
 
 
722
    m_Cached = false;
 
723
 
 
724
    m_State = Rendering;
 
725
    m_pTimer->start(0);
 
726
}
 
727
 
 
728
 
 
729
/*
 
730
 * This slot is connected to a timer event. It is called repeatedly until
 
731
 * the rendering is done.
 
732
 */
 
733
void KBackgroundRenderer::render()
 
734
{
 
735
    setBusyCursor(true);
 
736
    if (!(m_State & Rendering))
 
737
        return;
 
738
 
 
739
    if (!(m_State & InitCheck)) {
 
740
        QString f = cacheFileName();
 
741
        if (useCacheFile()) {
 
742
            QString w = m_pDirs->findResource("wallpaper", currentWallpaper());
 
743
            QFileInfo wi(w);
 
744
            QFileInfo fi(f);
 
745
            if (wi.lastModified().isValid() && fi.lastModified().isValid()
 
746
                    && wi.lastModified() < fi.lastModified()) {
 
747
                QImage im;
 
748
                if (im.load(f, "PNG")) {
 
749
                    m_Image = im;
 
750
                    m_Pixmap = QPixmap::fromImage(m_Image);
 
751
                    m_Cached = true;
 
752
                    m_State |= InitCheck | BackgroundDone | WallpaperDone;
 
753
                }
 
754
            }
 
755
        }
 
756
        m_pTimer->start(0);
 
757
        m_State |= InitCheck;
 
758
        return;
 
759
    }
 
760
 
 
761
    int ret;
 
762
 
 
763
    if (!(m_State & BackgroundDone)) {
 
764
        ret = doBackground();
 
765
        if (ret != Wait)
 
766
            m_pTimer->start(0);
 
767
        return;
 
768
    }
 
769
 
 
770
    // No async wallpaper
 
771
    doWallpaper();
 
772
 
 
773
    done();
 
774
    setBusyCursor(false);
 
775
}
 
776
 
 
777
 
 
778
/*
 
779
 * Rendering is finished.
 
780
 */
 
781
void KBackgroundRenderer::done()
 
782
{
 
783
    setBusyCursor(false);
 
784
    m_State |= AllDone;
 
785
    emit imageDone(screen());
 
786
    if (backgroundMode() == Program && m_pProc &&
 
787
            m_pProc->exitStatus() != QProcess::NormalExit) {
 
788
        emit programFailure(-1);
 
789
    } else if (backgroundMode() == Program && m_pProc &&
 
790
               m_pProc->exitCode()) {
 
791
        emit programFailure(m_pProc->exitStatus());
 
792
    } else if (backgroundMode() == Program) {
 
793
        emit programSuccess();
 
794
    }
 
795
}
 
796
 
 
797
/*
 
798
 * This function toggles a busy cursor on and off, for use in rendering.
 
799
 * It is useful because of the ASYNC nature of the rendering - it is hard
 
800
 * to make sure we don't set the busy cursor twice, but only restore
 
801
 * once.
 
802
 */
 
803
void KBackgroundRenderer::setBusyCursor(bool isBusy)
 
804
{
 
805
    if (m_isBusyCursor == isBusy)
 
806
        return;
 
807
    if (isBusy && !m_enableBusyCursor)
 
808
        return;
 
809
    m_isBusyCursor = isBusy;
 
810
    if (isBusy)
 
811
        QApplication::setOverrideCursor(QCursor(Qt::BusyCursor));
 
812
    else
 
813
        QApplication::restoreOverrideCursor();
 
814
}
 
815
 
 
816
/*
 
817
 * Stop the rendering.
 
818
 */
 
819
void KBackgroundRenderer::stop()
 
820
{
 
821
    if (!(m_State & Rendering))
 
822
        return;
 
823
 
 
824
    doBackground(true);
 
825
    doWallpaper(true);
 
826
    m_State = 0;
 
827
}
 
828
 
 
829
 
 
830
/*
 
831
 * Cleanup after rendering.
 
832
 */
 
833
void KBackgroundRenderer::cleanup()
 
834
{
 
835
    setBusyCursor(false);
 
836
    m_Background = QImage();
 
837
    m_Image = QImage();
 
838
    m_Pixmap = QPixmap();
 
839
    m_Wallpaper = QImage();
 
840
    delete m_pProc;
 
841
    m_pProc = 0L;
 
842
    m_State = 0;
 
843
    m_WallpaperRect = QRect();
 
844
    m_Cached = false;
 
845
}
 
846
 
 
847
 
 
848
void KBackgroundRenderer::setPreview(const QSize &size)
 
849
{
 
850
    if (size.isNull())
 
851
        m_bPreview = false;
 
852
    else {
 
853
        m_bPreview = true;
 
854
        m_Size = size;
 
855
    }
 
856
}
 
857
 
 
858
 
 
859
QPixmap KBackgroundRenderer::pixmap()
 
860
{
 
861
    if (m_State & AllDone) {
 
862
        if (m_Pixmap.isNull())
 
863
            m_Pixmap = QPixmap::fromImage(m_Image);
 
864
        return m_Pixmap;
 
865
    }
 
866
    return QPixmap();
 
867
}
 
868
 
 
869
QImage KBackgroundRenderer::image()
 
870
{
 
871
    if (m_State & AllDone) {
 
872
        if (m_Image.isNull())
 
873
            fullWallpaperBlend(); // create from m_Pixmap
 
874
        return m_Image;
 
875
    }
 
876
    return QImage();
 
877
}
 
878
 
 
879
 
 
880
void KBackgroundRenderer::load(int screen, bool drawBackgroundPerScreen, bool reparseConfig)
 
881
{
 
882
    if (m_State & Rendering)
 
883
        stop();
 
884
 
 
885
    cleanup();
 
886
    m_bPreview = false;
 
887
    m_Size = m_rSize;
 
888
 
 
889
    KBackgroundSettings::load(screen, drawBackgroundPerScreen, reparseConfig);
 
890
}
 
891
 
 
892
void KBackgroundRenderer::createTempFile()
 
893
{
 
894
    if (!m_Tempfile) {
 
895
        m_Tempfile = new KTemporaryFile();
 
896
        m_Tempfile->open();
 
897
    }
 
898
}
 
899
 
 
900
QString KBackgroundRenderer::cacheFileName()
 
901
{
 
902
    QString f = fingerprint();
 
903
    f.replace(':', '_'); // avoid characters that shouldn't be in filenames
 
904
    f.replace('/', '#');
 
905
    f = KStandardDirs::locateLocal("cache", QString("background/%1x%2_%3.png")
 
906
                                   .arg(m_Size.width()).arg(m_Size.height()).arg(f));
 
907
    return f;
 
908
}
 
909
 
 
910
bool KBackgroundRenderer::useCacheFile() const
 
911
{
 
912
    if (!enabled())
 
913
        return false;
 
914
    if (backgroundMode() == Program)
 
915
        return false; // don't cache these at all
 
916
    if (wallpaperMode() == NoWallpaper)
 
917
        return false; // generating only background patterns should be always faster
 
918
    QString file = currentWallpaper();
 
919
    if (file.endsWith(".svg") || file.endsWith(".svgz"))
 
920
        return true; // cache these, they can be bloody slow
 
921
    switch (backgroundMode()) {
 
922
    case NoWallpaper:
 
923
    case Centred:
 
924
    case Tiled:
 
925
    case CenterTiled:
 
926
        return false; // these don't need scaling
 
927
    case CentredMaxpect:
 
928
    case TiledMaxpect:
 
929
    case Scaled:
 
930
    case CentredAutoFit:
 
931
    case ScaleAndCrop:
 
932
    default:
 
933
        return true;
 
934
    }
 
935
}
 
936
 
 
937
void KBackgroundRenderer::saveCacheFile()
 
938
{
 
939
    if (!(m_State & AllDone))
 
940
        return;
 
941
    if (!useCacheFile())
 
942
        return;
 
943
    if (m_Image.isNull())
 
944
        fullWallpaperBlend(); // generate from m_Pixmap
 
945
    QString f = cacheFileName();
 
946
    if (KStandardDirs::exists(f) || m_Cached)
 
947
        utime(QFile::encodeName(f), NULL);
 
948
    else {
 
949
        m_Image.save(f, "PNG");
 
950
        // remove old entries from the cache
 
951
        QDir dir(KStandardDirs::locateLocal("cache", "background/"));
 
952
        const QFileInfoList list = dir.entryInfoList(QStringList() << "*.png", QDir::Files, QDir::Time | QDir::Reversed);
 
953
        if (!list.isEmpty()) {
 
954
            int size = 0;
 
955
            foreach (const QFileInfo &info, list)
 
956
                size += info.size();
 
957
            foreach (const QFileInfo &info, list) {
 
958
                if (size < 8 * 1024 * 1024)
 
959
                    break;
 
960
                // keep everything newer than 10 minutes if the total size is less than 50M (just in case)
 
961
                if (size < 50 * 1024 * 1024
 
962
                        && (time_t) info.lastModified().toTime_t() >= time(NULL) - 10 * 60)
 
963
                    break;
 
964
                size -= info.size();
 
965
                QFile::remove(info.absoluteFilePath());
 
966
            }
 
967
        }
 
968
    }
 
969
}
 
970
 
 
971
#include "bgrender.moc"