I have 2 images, I need to put one on top of the other.
The second image is taken on a pink background (simulated below) and due to the light falloff the background is more sort of a gradient.
I need to place the image on the other one and remove the background color. I would like to define a Hue-range that represents my background, and have every pixel that falls into this range removed/being transparent so that it is pasted on top as if it had a transparent background.
This is the sample image I would like to paste on any random image:
I am able to paste the image onto another image by using this:
' Draw from the source to the destination.
gr.DrawImage(fr_bm, to_rect, fr_rect, GraphicsUnit.Pixel)
(image, destination rectangle, source rectangle)
But I cannot figure out how to remove the background. Any help is greatly appreciated.
CodePudding user response:
This is a standard Color replacement filter (simplified -> no pre Convolution, since you just want to make transparent all pixels with colors that fall within a range).
It takes a source image, copies it to a 32Bit ARGB bitmap, then generates an identical container, used as destination bitmap.
All colors are compared to the Color specified in the colorFrom
argument and, if the Color's components are within a threshold defined by the tolerance
argument, the Color is replaced with the Color specified in the colorTo
argument.
The tolerance
value should be in the range (1:100)
(just because Photoshop and other graphic programs do that), the ColorReplacement
method normalizes this value on its own.
Possible results:
With the image in your example, with colorFrom
set to Color.Fucsia
and colorTo
set to Color.Transparent
, the green region is isolated with a tolerance of ~56
, then all remaining traces of the outer Color disappear (along with any anti-aliasing), between 80
and 90
. After that, also the green area begins to fade away. Around 95, you have a completely transparent Bitmap.
With a colorFrom
set to (255, 226, 18, 212)
, the same results appear at ~38
, then 60
to 70
(the replacement is more subtle).
Which means you have to pick a source color that gives better result, in your view and context.
Try it out passing different values to the method.
C# version:
public Bitmap ColorReplacement(Bitmap image, Color colorFrom, Color colorTo, float tolerance)
{
tolerance = (byte)(255.0f / 100.0f * Math.Max(Math.Min(100.0f, tolerance), 0.1f));
Bitmap source = new(image.Width, image.Height, PixelFormat.Format32bppArgb);
source.SetResolution(image.HorizontalResolution, image.VerticalResolution);
using (var g = Graphics.FromImage(source)) {
g.PixelOffsetMode = PixelOffsetMode.Half;
g.DrawImage(image, Point.Empty);
}
Bitmap destImage = new(source.Width, source.Height, PixelFormat.Format32bppArgb);
destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
foreach (PropertyItem item in image.PropertyItems) {
source.SetPropertyItem(item);
destImage.SetPropertyItem(item);
}
var dataFrom = source.LockBits(new Rectangle(0, 0, source.Width, source.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
var dataTo = destImage.LockBits(new Rectangle(0, 0, destImage.Width, destImage.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
byte[] buffer = new byte[Math.Abs(dataTo.Stride) * dataTo.Height];
Marshal.Copy(dataFrom.Scan0, buffer, 0, buffer.Length);
source.UnlockBits(dataFrom);
int bytesPerPixel = Image.GetPixelFormatSize(source.PixelFormat) / 8;
for (int pos = 0; pos < buffer.Length; pos = bytesPerPixel) {
byte blue = buffer[pos];
byte green = buffer[pos 1];
byte red = buffer[pos 2];
if ((blue < colorFrom.B tolerance && blue > colorFrom.B - tolerance) &&
(green < colorFrom.G tolerance && green > colorFrom.G - tolerance) &&
(red < colorFrom.R tolerance && red > colorFrom.R - tolerance)) {
int newBlue = colorFrom.B - blue colorTo.B;
int newGreen = colorFrom.G - green colorTo.G;
int newRed = colorFrom.R - red colorTo.R;
buffer[pos] = (byte)Math.Max(Math.Min(255, newBlue), 0);
buffer[pos 1] = (byte)Math.Max(Math.Min(255, newGreen), 0);
buffer[pos 2] = (byte)Math.Max(Math.Min(255, newRed), 0);
buffer[pos 3] = colorTo.A;
}
}
Marshal.Copy(buffer, 0, dataTo.Scan0, buffer.Length);
destImage.UnlockBits(dataTo);
return destImage;
}
VB.NET version:
Public Shared Function ColorReplacement(imageSource As Bitmap, colorFrom As Color, colorTo As Color, tolerance As Single) As Bitmap
tolerance = CByte(255.0F / 100.0F * Math.Max(Math.Min(100.0F, tolerance), 0.1F))
Dim source As New Bitmap(imageSource.Width, imageSource.Height, PixelFormat.Format32bppArgb)
source.SetResolution(imageSource.HorizontalResolution, imageSource.VerticalResolution)
Using g = Graphics.FromImage(source)
g.PixelOffsetMode = PixelOffsetMode.Half
g.DrawImage(imageSource, Point.Empty)
End Using
Dim destImage As Bitmap = New Bitmap(source.Width, source.Height, PixelFormat.Format32bppArgb)
destImage.SetResolution(imageSource.HorizontalResolution, imageSource.VerticalResolution)
For Each item As PropertyItem In imageSource.PropertyItems
source.SetPropertyItem(item)
destImage.SetPropertyItem(item)
Next
Dim dataFrom = source.LockBits(New Rectangle(0, 0, source.Width, source.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb)
Dim dataTo = destImage.LockBits(New Rectangle(0, 0, destImage.Width, destImage.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb)
Dim buffer As Byte() = New Byte(Math.Abs(dataFrom.Stride) * dataFrom.Height - 1) {}
Marshal.Copy(dataFrom.Scan0, buffer, 0, buffer.Length)
source.UnlockBits(dataFrom)
Dim bytesPerPixel As Integer = Image.GetPixelFormatSize(source.PixelFormat) \ 8
For pos As Integer = 0 To buffer.Length - 1 Step bytesPerPixel
Dim blue As Integer = buffer(pos)
Dim green As Integer = buffer(pos 1)
Dim red As Integer = buffer(pos 2)
If (blue < colorFrom.B tolerance AndAlso blue > colorFrom.B - tolerance) AndAlso
(green < colorFrom.G tolerance AndAlso green > colorFrom.G - tolerance) AndAlso
(red < colorFrom.R tolerance AndAlso red > colorFrom.R - tolerance) Then
Dim newBlue As Integer = colorFrom.B - blue colorTo.B
Dim newGreen As Integer = colorFrom.G - green colorTo.G
Dim newRed As Integer = colorFrom.R - red colorTo.R
buffer(pos) = CByte(Math.Max(Math.Min(255, newBlue), 0))
buffer(pos 1) = CByte(Math.Max(Math.Min(255, newGreen), 0))
buffer(pos 2) = CByte(Math.Max(Math.Min(255, newRed), 0))
buffer(pos 3) = colorTo.A
End If
Next
Marshal.Copy(buffer, 0, dataTo.Scan0, buffer.Length)
destImage.UnlockBits(dataTo)
Return destImage
End Function