drawImage bug


#1

We recently upgraded our juce code from version 1.47 to 1.51 and it seems we found a new bug in Graphics::drawImage method.
The code used to reproduce it is;

for (int i = 0; i < 100; i++)
{
	Image* testImage = new Image(Image::ARGB, sourceImage->getWidth(), sourceImage->getHeight(), true);
	{
		Graphics g(*testImage);
		g.drawImage(sourceImage, 0, 0, sourceImage->getWidth() + 1, sourceImage->getHeight() + 1,
			0, 0, sourceImage->getWidth() + 1, sourceImage->getHeight() + 1);
	}
	deleteAndZero(sourceImage);
	sourceImage = testImage;
}

The rightest column of pixels from sourceImage becomes grey for me after the loop finishes.
Best regards; Janos


#2

Eh? You’re repeatedly overlaying the same image on itself 100 times, right? If you do that with an ARGB image, then obviously any pixels whose alpha value is non-zero will drift to be less transparent and start to look muddy. That’s how all rendering engines behave - it’s just a natural rounding error when you’re working with 8-bit integers.

Just to be sure, I tried this and it did exactly what I’d expect, with identical results under my software renderer and the CoreGraphics renderer.

Maybe in the old code you didn’t see this because it was less accurate and rounded-down low alpha values, but the new stuff is definitely correct.


#3

Thank you for the quick answer.
Yes, I am repeatedly overlaying the same image on itself 100 times.
This is only a test code to reproduce the bug.
We are working on an image manipulation program; copying and forwarding the resulted images between algorithms is a common task and repeated as many times as needed.
So I changed my test code to start with an empty image before copying it. (previous version loaded the image from the disk)
I inserted these lines of codes before the others;

Image* sourceImage = new Image(Image::ARGB, newImage->getWidth(), newImage->getHeight(), true);
sourceImage->clear(0, 0, newImage->getWidth(), newImage->getHeight(), Colours::black);

In principle all pixels of the source image are opaque and black I think.
The result is the same.
I have the gray column of pixels at the right of the resulted image :frowning:
Best regards; Janos


#4

I can’t reproduce this. If you can give me some code that I can paste directly into the very latest tip version of the juce demo that will demonstrate this, then I’ll take a look.


#5

Hi Jules,

I work on the same project and as the problem remained unresolved I started to debug into the Juce code.

The problem comes from here, look at the

// plot the fist pixel of this segment, including any accumulated
// levels from smaller segments that haven’t been drawn yet

part of the code:

template <class EdgeTableIterationCallback>
void iterate (EdgeTableIterationCallback& iterationCallback) const throw()
{
    const int* lineStart = table;

    for (int y = 0; y < bounds.getHeight(); ++y)
    {
        const int* line = lineStart;
        lineStart += lineStrideElements;
        int numPoints = line[0];

        if (--numPoints > 0)
        {
            int x = *++line;
            jassert ((x >> 8) >= bounds.getX() && (x >> 8) < bounds.getRight());
            int levelAccumulator = 0;

            iterationCallback.setEdgeTableYPos (bounds.getY() + y);

            while (--numPoints >= 0)
            {
                const int level = *++line;
                jassert (((unsigned int) level) < (unsigned int) 256);
                const int endX = *++line;
                jassert (endX >= x);
                const int endOfRun = (endX >> 8);

                if (endOfRun == (x >> 8))
                {
                    // small segment within the same pixel, so just save it for the next
                    // time round..
                    levelAccumulator += (endX - x) * level;
                }
                else
                {
                    // plot the fist pixel of this segment, including any accumulated
                    // levels from smaller segments that haven't been drawn yet
                    levelAccumulator += (0xff - (x & 0xff)) * level;
                    levelAccumulator >>= 8;
                    x >>= 8;

                    if (levelAccumulator > 0)
                    {
                        if (levelAccumulator >> 8)
                            levelAccumulator = 0xff;

                        iterationCallback.handleEdgeTablePixel (x, levelAccumulator);
                    }

I don’t pretend understanding what you are doing here, but the debugger shows that for every first pixel of the block levelAccumulator becomes 254 (instead of 255), which is used as alpha in the rendering process. For any single image it is not perceptible. What my colleague tried to explain with his sample code that in our image processing pipeline these pixels with 254 alpha become weaker and weaker as we apply the chained effects and at the end these block border pixels become visibly transparent.

So this happens somewhere in the bit shift wizardry only for every first pixel of the block.

Thanks for having a look on this piece of your code, you will probably understand much faster how it could be fixed.

Bela


#6

Before I risk wasting my time looking at this, can you confirm that you’re using the very latest tip code from GIT?


#7

We use 1.51. I downloaded the tip, but it seems to me there is no real difference in that part of the code.

In the tip you write in the same place:

                   // plot the fist pixel of this segment, including any accumulated
                    // levels from smaller segments that haven't been drawn yet
                    levelAccumulator += (0xff - (x & 0xff)) * level;
                    levelAccumulator >>= 8;
                    x >>= 8;

                    if (levelAccumulator > 0)
                    {
                        if (levelAccumulator >> 8)
                            iterationCallback.handleEdgeTablePixelFull (x);
                        else
                            iterationCallback.handleEdgeTablePixel (x, levelAccumulator);
                    }

As the levelAccumulator calculation is the same it will provide the same 254 alpha and the new handleEdgeTablePixelFull (whatever it is) won’t be called when levelAccumulator is 254.

I may be wromg but it seems to me that if there is a bug it is still there.


#8

ok… (This stuff always makes my head hurt!)

I think that it just needs the 0xff to be changed to 0x100, like this:

levelAccumulator += (0x100 - (x & 0xff)) * level;

…?