0

I am trying to "blend" two transparent images together: one is for color, the second one is for shadow. Here are two images I have:

enter image description here enter image description here

And here is the result I am looking for:

enter image description here

Ignore the logo on the last one. I was trying to use this code https://softwarebydefault.com/2013/03/10/bitmap-blending/ but it produces this result: enter image description here

Can anyone point if I am using correct method? May be I should use some other algorithm to achieve required result?

Giorgi Nakeuri
  • 35,155
  • 8
  • 47
  • 75

2 Answers2

3

Rehshing old LockBits code this is the result

enter image description here

of this function:

public Bitmap Multiply(Bitmap bmp1, Bitmap bmp2)
{
    Size s1 = bmp1.Size;
    Size s2 = bmp2.Size;
    if (s1 != s2) return null;

    PixelFormat fmt1 = bmp1.PixelFormat;
    PixelFormat fmt2 = bmp2.PixelFormat;

    PixelFormat fmt = new PixelFormat();
    fmt = PixelFormat.Format32bppArgb;
    Bitmap bmp3 = new Bitmap(s1.Width, s1.Height, fmt);

    Rectangle rect = new Rectangle(0, 0, s1.Width, s1.Height);

    BitmapData bmp1Data = bmp1.LockBits(rect, ImageLockMode.ReadOnly, fmt1);
    BitmapData bmp2Data = bmp2.LockBits(rect, ImageLockMode.ReadOnly, fmt2);
    BitmapData bmp3Data = bmp3.LockBits(rect, ImageLockMode.ReadWrite, fmt);

    byte bpp1 = 4;
    byte bpp2 = 4;
    byte bpp3 = 4;

    if (fmt1 == PixelFormat.Format24bppRgb) bpp1 = 3;
    else if (fmt1 == PixelFormat.Format32bppArgb) bpp1 = 4; else return null;
    if (fmt2 == PixelFormat.Format24bppRgb) bpp2 = 3;
    else if (fmt2 == PixelFormat.Format32bppArgb) bpp2 = 4; else return null;

    int size1 = bmp1Data.Stride * bmp1Data.Height;
    int size2 = bmp2Data.Stride * bmp2Data.Height;
    int size3 = bmp3Data.Stride * bmp3Data.Height;
    byte[] data1 = new byte[size1];
    byte[] data2 = new byte[size2];
    byte[] data3 = new byte[size3];
    System.Runtime.InteropServices.Marshal.Copy(bmp1Data.Scan0, data1, 0, size1);
    System.Runtime.InteropServices.Marshal.Copy(bmp2Data.Scan0, data2, 0, size2);
    System.Runtime.InteropServices.Marshal.Copy(bmp3Data.Scan0, data3, 0, size3);

    for (int y = 0; y < s1.Height; y++)
    {
        for (int x = 0; x < s1.Width; x++)
        {
            int index1 = y * bmp1Data.Stride + x * bpp1;
            int index2 = y * bmp2Data.Stride + x * bpp2;
            int index3 = y * bmp3Data.Stride + x * bpp3;
            Color c1, c2;

            if (bpp1 == 4)
                c1 = Color.FromArgb(data1[index1 + 3], data1[index1 + 2], data1[index1 + 1], data1[index1 + 0]);
            else c1 = Color.FromArgb(255, data1[index1 + 2], data1[index1 + 1], data1[index1 + 0]);
            if (bpp2 == 4)
                c2 = Color.FromArgb(data2[index2 + 3], data2[index2 + 2], data2[index2 + 1], data2[index2 + 0]);
            else c2 = Color.FromArgb(255, data2[index2 + 2], data2[index2 + 1], data2[index2 + 0]);

            data3[index3 + 0] = (byte)( c1.B * c2.B / 256);
            data3[index3 + 1] = (byte)( c1.G * c2.G / 256);
            data3[index3 + 2] = (byte)( c1.R * c2.R / 256);
            data3[index3 + 3] = c1.A;
        }
    }

    System.Runtime.InteropServices.Marshal.Copy(data3, 0, bmp3Data.Scan0, data3.Length);
    bmp1.UnlockBits(bmp1Data);
    bmp2.UnlockBits(bmp2Data);
    bmp3.UnlockBits(bmp3Data);
    return bmp3;
}

Note that the alpha channel is copied from the 1st bitmap.

Instead of 'Multiply' 'ColorBurn' is also a good candidate. Here is very nice write-up of the math for the various blending modes.

Update: Using Anna's algorithm

    data3[index3 + 0] = (byte)(c2.B + (c1.B * (255 - c2.B) / 256f));
    data3[index3 + 1] = (byte)(c2.G + (c1.G * (255 - c2.G) / 256f));
    data3[index3 + 2] = (byte)(c2.R + (c1.R * (255 - c2.R) / 256f));

results in this lighter image:

enter image description here

To keep the red colors everywhere and still make the result lighter I would apply a little gamma correction with a Color Matrix.

Community
  • 1
  • 1
TaW
  • 53,122
  • 8
  • 69
  • 111
  • do you know that you are the MAN?! Thanks so much! That is exactly what I needed, works like a charm. – Giorgi Nakeuri Sep 12 '16 at 18:19
  • Wow, legandary. Just wondering if my algorithm worked BTW. xD – Kushonoha Sep 12 '16 at 18:35
  • @Anna: I did try and while nice, the result was color inverted. See my update! – TaW Sep 12 '16 at 18:39
  • Oh no xD forgot that RGB gives more light on higher values :P Thanks a lot anyway. – Kushonoha Sep 12 '16 at 18:46
  • @Anna: I have now coded your formula correctly and it looks also nice, no longer inverted, but maybe a little too 'transparent' for a t-shirt ;-) – TaW Sep 12 '16 at 19:08
  • Oh, I was actually thinking about the general idea of lowering the RGBA values of the shade images until white gets red, but as I mistakenly took #000 as white, the whole thing got inverted. Anyway, OffTopic: if you have any expertise in UWP, can you take a look at my latest question? I'm completely doomed, thanks. – Kushonoha Sep 12 '16 at 19:16
  • Sorry completely out of my depth there :-( – TaW Sep 12 '16 at 19:22
2

Sorry for ignoring that blog, I actually read it a bit but had my own algorithm to come up with, you can ignore me as well xD.

It is simple, for each pixel:

  1. In the main image (red) find the RGBA values, let's call it ColorM.
  2. In the shade image, find the RGBA values, let's call it ColorS.
  3. For each of R, G, B and A values, calculate ((255 - ColorM) * ColorS) / 255, let's call the final RGBA value ColorOver.
  4. Calculate ColorM + ColorOver and it will give the final image.

Test it if you wanted, seems a bit buggy.

Kushonoha
  • 196
  • 2
  • 15
  • Thanks Anna, I was just trying to implement A*B/255 but it is not quite what I am looking for. Let me check your formula... – Giorgi Nakeuri Sep 12 '16 at 18:09