D2D: SVG dragging issue

There seems to be an issue while dragging SVG icons (in this case with the toolbar editor present in the JUCE toolbar).

During the drag the icon does not show in D2D:

toolbar_edit_d2d

As opposed to the normal behavior:

toolbar_edit

1 Like

Hi Marcel-

Thanks for reporting. Looks like the issue has to do with how the drag image is constructed.

Search for the function imageToUse inside of DragAndDropContainer::startDragging in juce_DragAndDropContainer.cpp. Try enclosing each Graphics object in curly braces:

    Image fade (Image::SingleChannel, image.getWidth(), image.getHeight(), true);
    { // <-- add this brace
        Graphics fadeContext(fade);

        ColourGradient gradient;
        gradient.isRadial = true;
        gradient.point1 = clipped.toFloat() * scaleFactor;
        gradient.point2 = gradient.point1 + Point<float>(0.0f, scaleFactor * 400.0f);
        gradient.addColour(0.0, Colours::white);
        gradient.addColour(0.375, Colours::white);
        gradient.addColour(1.0, Colours::transparentWhite);

        fadeContext.setGradientFill(gradient);
        fadeContext.fillAll();
    } // <-- close here so that Direct2D can finish drawing

    Image composite (Image::ARGB, image.getWidth(), image.getHeight(), true);
    { // <-- and this one
        Graphics compositeContext(composite);

        compositeContext.reduceClipRegion(fade, {});
        compositeContext.drawImageAt(image, 0, 0);
    }

Matt

That worked like a charm!

Right on! Nice find.

This change is now available on the direct2d branch, along with miscellaneous fixes for images.

Direct2D works differently than the software renderer. With the software renderer, any drawing operations take effect immediately; if you call g.drawEllipse(…), the ellipse is fully drawn by the time drawEllipse returns.

With Direct2D, you’re actually building a queue of deferred drawing operations. The actual drawing doesn’t happen until the renderer tells Direct2D that it’s done building the queue, which in this case happens when the Graphics object goes out of scope.

This is really only a concern when painting to an Image. If you’re painting on an Image, be sure to enclose your Graphics object appropriately.

Matt

1 Like

What would be an efficient way if you’d have to draw every pixel in an image (like in a sonogram)?

Is every pixel a different color?

Matt

Hi Marcel-

One simple option is just to draw lots of little 1x1 rectangles. I took the JUCE SimpleFFTDemo and did a quick rewrite (see below).

I posted the rewrite on the Direct2D test repo: GitHub - mattgonzalez/JUCEDirect2DTest: JUCE Direct2D test apps & documentation

You could also use a ColourGradient, or bitmapped sprites, or a cached path.

Matt

void drawNextLineOfSpectrogram()
{
    auto imageHeight = spectrogramImage.getHeight();

    // then render our FFT data..
    forwardFFT.performFrequencyOnlyForwardTransform (fftData);

    // find the range of values produced, so we can scale our rendering to
    // show up the detail clearly
    auto maxLevel = FloatVectorOperations::findMinAndMax (fftData, fftSize / 2);

    //
    // Fill lots of little rectangles
    //
    {
        Graphics g{ spectrogramImage };

        for (auto y = 1; y < imageHeight; ++y)
        {
            auto skewedProportionY = 1.0f - std::exp(std::log((float)y / (float)imageHeight) * 0.2f);
            auto fftDataIndex = jlimit(0, fftSize / 2, (int)(skewedProportionY * (int)fftSize / 2));
            auto level = jmap(fftData[fftDataIndex], 0.0f, jmax(maxLevel.getEnd(), 1e-5f), 0.0f, 1.0f);

            g.setColour(Colour::fromHSV(level, 1.0f, level, 1.0f));
            g.fillRect(column, y, 1, 1);
        }
    }

    column = (column + 1) % spectrogramImage.getWidth();
}
1 Like

In general, I recommend against directly accessing the pixel data (BitmapData, setPixelAt, getPixelAt, etc).

Images are stored in the GPU by default, so if you want to access the pixel data the renderer has to map them back from the GPU into the CPU, which is slow.

Of course, sometimes you do have to read and write the pixel data directly. In that case, consider making a software image, and then painting that software image to your window.

You can explicitly create a software image like this:

    auto softwareImage = Image{ SoftwareImageType{}.create(Image::ARGB, 100, 100, true) };

Matt

2 Likes