-
 KDE-Apps.org Applications for the KDE-Desktop 
 GTK-Apps.org Applications using the GTK Toolkit 
 GnomeFiles.org Applications for GNOME 
 MeeGo-Central.org Applications for MeeGo 
 CLI-Apps.org Command Line Applications 
 Qt-Apps.org Free Qt Applications 
 Qt-Prop.org Proprietary Qt Applications 
 Maemo-Apps.org Applications for the Maemo Plattform 
 Java-Apps.org Free Java Applications 
 eyeOS-Apps.org Free eyeOS Applications 
 Wine-Apps.org Wine Applications 
 Server-Apps.org Server Applications 
 apps.ownCloud.com ownCloud Applications 
--
-
 KDE-Look.org Artwork for the KDE-Desktop 
 GNOME-Look.org Artwork for the GNOME-Desktop 
 Xfce-Look.org Artwork for the Xfce-Desktop 
 Box-Look.org Artwork for your Windowmanager 
 E17-Stuff.org Artwork for Enlightenment 
 Beryl-Themes.org Artwork for the Beryl Windowmanager 
 Compiz-Themes.org Artwork for the Compiz Windowmanager 
 EDE-Look.org Themes for your EDE Desktop 
--
-
 Debian-Art.org Stuff for Debian 
 Gentoo-Art.org Artwork for Gentoo Linux 
 SUSE-Art.org Artwork for openSUSE 
 Ubuntu-Art.org Artwork for Ubuntu 
 Kubuntu-Art.org Artwork for Kubuntu 
 LinuxMint-Art.org Artwork for Linux Mint 
 Arch-Stuff.org Art And Stuff for Arch Linux 
 Frugalware-Art.org Themes for Frugalware 
 Fedora-Art.org Artwork for Fedora Linux 
 Mandriva-Art.org Artwork for Mandriva Linux 
--
-
 KDE-Files.org Files for KDE Applications 
 OpenTemplate.org Documents for OpenOffice.org
 GIMPStuff.org Files for GIMP
 InkscapeStuff.org Files for Inkscape
 ScribusStuff.org Files for Scribus
 BlenderStuff.org Textures and Objects for Blender
 VLC-Addons.org Themes and Extensions for VLC
--
-
 KDE-Help.org Support for your KDE Desktop 
 GNOME-Help.org Support for your GNOME Desktop 
 Xfce-Help.org Support for your Xfce Desktop 
--
openDesktop.orgopenDesktop.org:   Applications   Artwork   Linux Distributions   Documents    LinuxDaily.com    Linux42.org    OpenSkillz.com   
 
Apps
News
Groups
Knowledge
Events
Forum
People
Jobs
Register
Login

-
- News . 
0
votes
click to vote up

Aurélien Gâteau: Fixing a subtle animation bug in Dolphin


Published Mar 27 2013 via RSS

Noticing the bug

Some time ago I worked on a problem with the mouse over animation in Dolphin. It was mostly visible with hidden files, because icons for hidden files are semi transparent. The bug caused icons for hidden files to "blink" when one moved the mouse over them.

I thought the fix was simple, filed this review request, which got in.

But that was too easy. Two weeks ago it was pointed out to me that the fix was wrong, and was causing a blink at the end of the hover animation. It's the kind of subtle bugs which you don't see at first, but can't ignore once it has been pointed out to you. Here is a short video demonstrating it:

dolphin-before.webm

Understanding the bug

How does one debug such a bug? If you are smart enough, you look at the code, let it sink in you and at some point yell: "AH! THE BUG IS THERE!"

On the other hand, if you're like me, you need more information. Over the years I built a few techniques to help debugging graphical issues. Here are the ones I used there.

This is the code we are interested in. It comes from dolphin/src/kitemviews/kstandarditemlistwidget.cpp, in the paint() method:

const KItemListStyleOption& itemListStyleOption = styleOption();
if (isHovered()) {
    const qreal opacity = painter->opacity();
    // Blend the unhovered and hovered pixmap if the hovering
    // animation is ongoing
    if (hoverOpacity() < 1.0) {
        painter->setOpacity((1.0 - hoverOpacity()) * opacity);
        drawPixmap(painter, m_pixmap);
    }

    painter->setOpacity(hoverOpacity() * opacity);
    drawPixmap(painter, m_hoverPixmap);
    painter->setOpacity(opacity);
} else {
    drawPixmap(painter, m_pixmap);
}

Note: drawPixmap(QPainter*, QPixmap) is equivalent to QPainter::drawPixmap() as far as we are concerned

Save the pixmaps

When faced with graphical bugs, a trick I often use is to save pixmaps to disk for review.

In our case, we would like to know what gets painted on the widget at the end of the if (isHovered()) block.

First step is to reproduce all paint operations on a QPixmap.

     const KItemListStyleOption& itemListStyleOption = styleOption();
+    QPixmap pix(option->rect.size());
+    pix.fill(Qt::transparent);
     if (isHovered()) {
+        QPainter painter2(&pix);
         const qreal opacity = painter->opacity();
         // Blend the unhovered and hovered pixmap if the hovering
         // animation is ongoing
         if (hoverOpacity() < 1.0) {
             painter->setOpacity((1.0 - hoverOpacity()) * opacity);
             drawPixmap(painter, m_pixmap);
+            painter2.setOpacity((1.0 - hoverOpacity()) * opacity);
+            drawPixmap(&painter2, m_pixmap);
         }

         painter->setOpacity(hoverOpacity() * opacity);
         drawPixmap(painter, m_hoverPixmap);
+
+        painter2.setOpacity(hoverOpacity() * opacity);
+        drawPixmap(&painter2, m_hoverPixmap);
+
         painter->setOpacity(opacity);
     } else {
         drawPixmap(painter, m_pixmap);

Now that we have the content of the widget in a QPixmap, we must save it. This is where one has to be creative to come up with a way to generate names which do not step on each others: since we are saving images for an animation, we can't use something like /tmp/image.png, otherwise we will only get the last frame.

I decided for this scheme: /tmp/$filename-$hoverOpacity.png. Here are the new changes:

         painter2.setOpacity(hoverOpacity() * opacity);
         drawPixmap(&painter2, m_hoverPixmap);
+        painter2.end();
+
+        const TextInfo* textInfo = m_textInfo.value("text");
+        QString itemName = textInfo->staticText.text();
+        QString name = QString("/tmp/%1-%2.png").arg(itemName).arg(int(hoverOpacity() * 1000));
+        pix.save(name);

         painter->setOpacity(opacity);
     } else {

Note the call to painter2.end(): the painter must not be active anymore when saving the pixmap to disk.

After applying those changes, I restarted Dolphin, moved the mouse over an item, close Dolphin and looked inside /tmp to find a bunch of images here.

Sometimes looking at the images is enough to get a hint at what the problem is, but unfortunately, not this time. The differences between two consecutive frames was too small for me to understand what's going on.

I needed images which would be easier to analyze and compare. This brings us to the second technique I used.

Easy-to-analyze images

What do I mean with easy-to-analyze images? I wanted images I can inspect with a color picker and draw conclusions from.

Since the bug is about a transition between two images, I decided to change the images as following: divide the images in 3 columns, then replace the content of m_pixmap with an image whose columns 1 and 2 are solid red (#ff0000) and replace the content of m_hoverPixmap with an image whose columns 2 and 3 are solid blue (#0000ff).

These images would be much easier to analyze because they make it possible to watch how m_pixmap fades out by looking at the red channel in column 1, how m_hoverPixmap fades in by looking at the blue channel in column 3, and how the two mixes by looking at the red and blue channels in column 2.

Here are the necessary changes:

+    {
+        m_pixmap.fill(Qt::transparent);
+        QPainter p(&m_pixmap);
+        p.fillRect(0, 0, m_pixmap.width() * 2 / 3, m_pixmap.height(), Qt::red);
+    }
+    {
+        m_hoverPixmap.fill(Qt::transparent);
+        QPainter p(&m_hoverPixmap);
+        p.fillRect(m_hoverPixmap.width() / 3, 0, m_hoverPixmap.width() * 2 / 3, m_hoverPixmap.height(), Qt::blue);
+    }
     const KItemListStyleOption& itemListStyleOption = styleOption();
     QPixmap pix(option->rect.size());
     pix.fill(Qt::transparent);

And the result:

Dolphin with debug pixmaps

Isn't this beautiful? :)

I could now look at those images more closely. I like to use GIMP for this, because its color picker provides the value of the alpha channel and it allows to load several images as layers with "File > Open as Layers...".

Here is GIMP with all images loaded as layers, showing the color of column 2 when hoverOpacity is 0.532:

Debug pixmaps in GIMP

An opacity of 0.532 means the image should roughly be half red and half blue. Furthermore, the middle column should still be fully opaque since this column is fully opaque on both m_pixmap and m_hoverPixmap.

But this is not what is happening here: red is at 29%, blue is 71% and alpha is 74%. This gives more insight: the red image (m_pixmap) is fading out faster than the blue image (m_hoverPixmap) is fading in.

Time for some maths

(In this section, to keep formulas short, I am using pixA instead of m_pixmap, pixB instead of m_hoverPixmap, bg for the background and v instead of hoverOpacity())

Before my first fix, Dolphin was doing something like this:

  1. painter.drawPixmap(0, 0, pixA);
  2. painter.setOpacity(v);
  3. painter.drawPixmap(0, 0, pixB);

As v increases from 0 to 1, pixB gradually appears over pixA. pixA never goes away, it is still visible behind pixB.

My first fix changed it to something like this:

  1. painter.setOpacity(1.0 - v);
  2. painter.drawPixmap(0, 0, pixA);
  3. painter.setOpacity(v);
  4. painter.drawPixmap(0, 0, pixB);

This is wrong as well. Here is why:

What we want to do is a linear interpolation between two pixmaps, where pixA slowly fades out as pixB fades in.

Let's assume our background and pixmaps are fully opaque (it simplifies the math but still exposes the flaw).

Linear interpolation between two alpha-premultiplied images pixA and pixB with a factor of k is defined with the following formula, where c(x) is any of the ARGB component of pixel x, normalized to range 0..1:

c(result) = c(pixB) * k + c(pixA) * (1 - k)

The default composition mode of QPainter, SourceOver, uses the following formula:

c(result) = c(pixB) * k + c(pixA) * (1 - alpha(pixB) * k)

Note: this formula is only valid for alpha-premultiplied images, see QImage doc for more details

If pixA and pixB are fully opaque, it can be simplified to:

c(result) = c(pixB) * k + c(pixA) * (1 - k)

This is the same as the interpolation formula, what could be wrong?

After line 2, the widget shows pixA painted over the background using the SourceOver formula, with k = 1 - v. The temporary appearance is thus:

c(tmp) = c(pixA) * (1 - v)  +  c(bg) * (1 - (1 - v))
       = c(pixA) * (1 - v)  +  c(bg) * v

After line 4, the widget shows pixB painted over tmp with k = v. The final pixels are thus:

c(final) = c(pixB) * v  +  c(tmp) * (1 - v)
         = c(pixB) * v  +  (c(pixA) * (1 - v) + c(bg) * v) * (1 - v)
         = c(pixB) * v  +  c(pixA) * (1 - v)^2  +  c(bg) * v * (1 - v)

Which is different from the expected:

           c(pixB) * v  +  c(pixA) * (1 - v)

Both line 2 and line 4 multiplied c(pixA) by 1 - v, leading to it being multiplied by (1 - v)^2, letting the background shows in by v * (1 - v).

Fixing the bug

To get the correct result, we need to apply a different painting formula. Unfortunately, QPainter does not provides an Interpolation mode. It does however provide a Plus mode, which simply does the sum of each components:

c(plus) = c(pixA) + c(pixB)

This makes it possible to implement interpolation this way:

First, paint m_pixmap, reduced by 1 - hoverOpacity() in a transparent pixmap:

QPixmap pixmap1(option->rect.size());
pixmap1.fill(Qt::transparent);
{
    QPainter p(&pixmap1);
    p.setOpacity(1.0 - hoverOpacity());
    drawPixmap(&p, m_pixmap);
}

Do the same for m_hoverPixmap, reduced by hoverOpacity():

QPixmap pixmap2(option->rect.size());
pixmap2.fill(Qt::transparent);
{
    QPainter p(&pixmap2);
    p.setOpacity(hoverOpacity());
    drawPixmap(&p, m_hoverPixmap);
}

Then, paint pixmap2 on pixmap1, using CompositionMode_Plus:

{
    QPainter p(&pixmap1);
    p.setCompositionMode(QPainter::CompositionMode_Plus);
    p.drawPixmap(0, 0, pixmap2);
}

Now pixmap1 is m_hoverPixmap * hoverOpacity() + m_pixmap * (1.0 - hoverOpacity()). We can paint pixmap1 on the widget:

painter->drawPixmap(0, 0, pixmap1);

That's it! This version works as expected:

dolphin-after.webm

Dolphin from KDE SC 4.10.2 includes this fix. Hope you enjoy your smooth mouse over animations!

Flattr this



BackRead original postSend to a friend

Add comment

Add comment
Show all posts




-
 
 
 Who we are
Contact
More about us
Frequently Asked Questions
Register
Twitter
Blog
Explore
Apps
Jobs
Knowledge
Events
People
Updates on identi.ca
Updates on Twitter
Facebook App
Content RSS   
Events RSS   

Participate
Groups
Forum
Add App
Public API
About GTK-Apps.org
Legal Notice
Spreadshirt Shop
CafePress Shop
Advertising
Sponsor us
Report Abuse
 

Copyright 2007-2014 GTK-Apps.org Team  
All rights reserved. GTK-Apps.org is not liable for any content or goods on this site.
All contributors are responsible for the lawfulness of their uploads.