haganbmj / MagicSetEditor2

Magic Set Editor is a program for designing trading cards
Other
21 stars 8 forks source link

linear_blend function does not blend alpha channels #82

Closed G-e-n-e-v-e-n-s-i-S closed 3 months ago

G-e-n-e-v-e-n-s-i-S commented 3 months ago

If I blend the following two images: square and circle with this call:

linear_blend(image1: "square.png", image2: "circle.png", x1: 0.4, x2: 0.6, y1: 0, y2: 0)

I get the following result: result

It uses the alpha channel of the image1 parameter, and ignores the alpha channel of the image2 parameter. The pixels which should be transparent on the circle are replaced with black pixels (their correct value, but ignoring the alpha).

The masked_blend function seems to have the same problem. It uses the alpha channel of the light parameter, and ignores the one from the dark parameter.

G-e-n-e-v-e-n-s-i-S commented 3 months ago

Workaround if the images that must be blended are known:

haganbmj commented 3 months ago

The source for linear blend is here I believe. https://github.com/haganbmj/MagicSetEditor2/blob/ddd49b50d7d947e4451d31243ea29d7778c16884/src/gfx/blend_image.cpp#L18-L59

I don't really know the math behind all this, but I know transparency in general is a weak point with MSE. I think it treats most things internally as jpg?

G-e-n-e-v-e-n-s-i-S commented 3 months ago

Okay, so I don't know c++, so I'm kind of flailing like a headless chicken here, but looking at this other file, it seems it's not forgetting the alpha (it says it right there in the comment!):

https://github.com/haganbmj/MagicSetEditor2/blob/ddd49b50d7d947e4451d31243ea29d7778c16884/src/gfx/rotate_image.cpp#L24-L41

So maybe adapting this to here would give something like this, to be added at the end of what you linked:

if (img1.HasAlpha() && img2.HasAlpha()) { 
   data1 = img1.GetAlpha();
   data2 = img2.GetAlpha();
   for (int y = 0 ; y < height ; ++y) { 
     for (int x = 0 ; x < width ; ++x) { 
       int mult = x * xm + y * ym + d; 
       if (mult < 0)      mult = 0; 
       if (mult > fixed)  mult = fixed; 
       data1[0] = data1[0] + mult * (data2[0] - data1[0]) / fixed;
       data1 += 1; 
       data2 += 1; 
     } 
   } 
 }
haganbmj commented 3 months ago

That appears to work, nice work inferring that. I'm going to poke around and see if I can spot anything that breaks.

image

haganbmj commented 3 months ago

I'm not sure what masked_blend should look like, never used these functions before. Took a stab at it, but not sure it's the right approach.

https://github.com/haganbmj/MagicSetEditor2/pull/83

The below is just averaging the alpha values, which I think would be equivalent to a 50% point in the linear blend? (a + b) / 2 image

G-e-n-e-v-e-n-s-i-S commented 3 months ago

In general, the alpha channel should be blended exactly like the other three channels, so maybe something like:

void mask_blend(Image& img1, const Image& img2, const Image& mask) {
  int width = img1.GetWidth(), height = img1.GetHeight();
  if (img2.GetWidth() != width || img2.GetHeight() != height) {
    throw Error(_("Images used for blending in masked_blend function must have the same size"));
  }
  if (mask.GetWidth() != width || mask.GetHeight() != height) {
    throw Error(_("Mask used for blending in masked_blend function must have the same size as the images"));
  }

  UInt size = width * height;
  // these have the following structure:
  // [pixel1red, pixel1green, pixel1blue, pixel2red, pixel2green, pixel2blue, pixel3red, etc...]
  Byte *data1 = img1.GetData(), *data2 = img2.GetData(), *dataM = mask.GetData();
  // for each subpixel...
  for (UInt i = 0 ; i < (size * 3) ; ++i) {
    data1[i] = (data1[i] * dataM[i] + data2[i] * (255 - dataM[i])) / 255;
  }

  if (img1.HasAlpha() && img2.HasAlpha()) { 
    // these have the following structure:
    // [pixel1alpha, pixel2alpha, pixel3alpha, etc...]
    Byte *alpha1 = img1.GetAlpha(), *alpha2 = img2.GetAlpha();
    for (UInt i = 0 ; i < size ; ++i) {
      // use mask's red channel to blend alpha (all mask channels should be identical since it's grey scale)
      alpha1[i] = (alpha1[i] * dataM[i*3] + alpha2[i] * (255 - dataM[i*3])) / 255;
    }
  }
}
haganbmj commented 3 months ago

Yeah, realized that averaging was definitely wrong while I was thinking it over today. Lemme poke around with that and see what I can get together.

haganbmj commented 3 months ago

Github closed this automatically when I merged in the other PR. I've got a pre-release build up, was hoping you could double check that it behaves the way you expect it to - just used your snippet as is.

https://github.com/haganbmj/MagicSetEditor2/releases/tag/v2.5.0

G-e-n-e-v-e-n-s-i-S commented 3 months ago

just used your snippet as is.

That's a bold move, you like living dangerously. Did some quick tests, it seems to be behaving correctly. Will do more tests these coming days.

Thank you so much for your quick reaction time and your help, much appreciated!

haganbmj commented 3 months ago

just used your snippet as is.

That's a bold move, you like living dangerously. Did some quick tests, it seems to be behaving correctly. Will do more tests these coming days.

Thank you so much for your quick reaction time and your help, much appreciated!

For whatever reason this one just caught my interest, I've been pretty absent from MSE and Magic in general the last year or two otherwise.

G-e-n-e-v-e-n-s-i-S commented 3 months ago

I've been pretty absent from MSE and Magic in general the last year or two otherwise.

No worries. Anyway, did a bit more testing, everything seems fine so I'm closing this. Thanks again!