Jump to content

Brush Factory (Beta) help needed


Recommended Posts

Download here (too large to attach): Brush Factory (Beta)

 

Brush Factory allows you to draw brushes with basic dynamics, like randomized scaling, rotation, and transparency. Example below.

K5Rxia4.png

 

It works well on images that are absolutely opaque. But, if the image has any transparency at all, it begins to cause strange errors. Consider this.

zX4oFHZ.png8edgUK3.png

Notice the obvious gray border around the white brush stroke on the second image. That shouldn't happen. These colors are actually darker (they are not white anymore, and it's not the alpha making it look gray there). This exact type of error occurs when you simply open/close the dialog, undo/redo, and draw over transparent regions. It can be hard to see esp. when simply opening and OKing the dialog, but it's there, so don't use Brush Factory with transparency.

 

Anyway, I'd love to get help with fixing this GDI+ issue. I've been here:

http://stackoverflow.com/questions/6615340/color-value-with-alpha-of-zero-shows-up-as-black

http://stackoverflow.com/questions/15956424/gdi-bug-anti-aliasing-white-on-transparency

http://stackoverflow.com/questions/1861305/c-sharp-resized-images-have-black-borders

http://stackoverflow.com/questions/1890605/ghost-borders-ringing-when-resizing-in-gdi

http://stackoverflow.com/questions/2024757/how-to-solve-grayish-frame-issue-when-scaling-a-bitmap-using-gdi

http://stackoverflow.com/questions/6744067/drawimage-function-over-winforms-does-not-work-correctly

http://www.codeproject.com/Articles/14884/BorderBug

 

I read that there is a Border Bug where resized images include pixels outside the image, which are assumed Black, and involve them in the calculation (resulting in blackened borders). I read that creating a new bitmap automatically premultiplies each pixel by its alpha, so I've tried a tested workaround that locks the bits and copies them over (to avoid multiplying by alpha), but this doesn't solve my issue.

 

I've tried all the solutions presented here, which I summarize below:

- Any combination of InterpolationMode.NearestNeighbor, SmoothingMode.None, and PixelOffsetMode.Half.

- Clearing with a transparent color, Color.White, and Color.Black, along with the above attempts as well.

- Workarounds for cloning bitmaps, sometimes in conjunction with other workarounds above.

- Using an ImageAttributes object and setting WrapMode to TileFlipXY, sometimes in conjunction with above workarounds.

 

I've attached the source code along with the .dll. Please try to help if you can! Otherwise, I'll have no choice but to port the entire thing to another graphics library that doesn't have these silly errors and it'll take ages. Feel free to point out any side bugs so I can fix them too; I know a few of them already but am focusing on this now.

 

Thanks.

 

Download here (too large to attach): Brush Factory (Beta)

Edited by AnthonyScoffler
Replaced dead links with permanent links on google drive
  • Upvote 2
Link to comment
Share on other sites

This looks like a premultiplied alpha problem. As in, something is premultiplied when it shouldn't be, or vice versa.

 

What are you using to draw the brush stroke?

 

Are the pixel values of the brush all white, with varying alpha? ("white" meaning red=255,green=255,blue=255) Or are they premultiplied, such that A=R=G=B, but they are less than 255?

 

You might play around with using ColorBgra.Convert[From|To]PremultipliedAlpha(). This will involve locking your bitmap, then casting the BitmapData.Scan0 to a ColorBgra*.

The Paint.NET Blog: https://blog.getpaint.net/

Donations are always appreciated! https://www.getpaint.net/donate.html

forumSig_bmwE60.jpg

Link to comment
Share on other sites

This looks like a premultiplied alpha problem.

Yep! I've been thinking that for awhile now. It's peculiar because all images are kept as 32bppARGB. The premultiplied format is 32bppPARGB, so I'm not sure why they're being premultiplied. I read that images are premultiplied when they're created (that the alpha blend function from C++ was incorporated like that in the C# version of GDI+, which I would call a design flaw).

What are you using to draw the brush stroke?

//Something along the lines of this:
Graphics g = new Graphics.FromImage(the_entire_image);
g.DrawImage(the_brush_image, xPos, yPos);

Are the pixel values of the brush all white, with varying alpha?

No, which is why I think it's premultiplied alpha causing problems (it's the main symptom). The colors are darker and the more I draw, the darker they get (all the way to black). The alpha increases with each brush stroke, of course, but that's fully expected.

 

You might play around with using ColorBgra.Convert[From|To]PremultipliedAlpha()

I wrote the code correctly, but it doesn't solve my problem. I tried FromPremultipliedAlpha, which tends to increase aliasing. The other is less effective yet. I tried applying it once and twice, to the brush before/after rotation, or both. Here is the code I used.

public static unsafe void UndoPremultipliedAlpha(ref Bitmap img)
{
    if (img.PixelFormat != PixelFormat.Format32bppArgb)
    {
        return;
    }

    BitmapData bmpData = img.LockBits(
        new Rectangle(0, 0,
            img.Width,
            img.Height),
        ImageLockMode.ReadWrite,
        img.PixelFormat);

    //The top left pixel's address, from which the rest can be found.
    byte* row = (byte*)bmpData.Scan0;

    //Iterates through each pixel to apply the change.
    for (int y = 0; y < img.Height; y++)
    {
        for (int x = 0; x < img.Width; x++)
        {
            int pos = y * bmpData.Stride + x * 4;

            //Gets the pixel color, corrects it, and applies changes.
            ColorBgra col = ColorBgra.FromBgra(
                row[pos],
                row[pos + 1],
                row[pos + 2],
                row[pos + 3]);
           
            col = col.ConvertFromPremultipliedAlpha();

            row[pos] = col.B;
            row[pos + 1] = col.G;
            row[pos + 2] = col.R;
            row[pos + 3] = col.A;
        }
    }

    img.UnlockBits(bmpData);
}
Edited by AnthonyScoffler
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...