Reputation: 11323
I have two images the first one is with white background and the size is 512x512 type png Bit depth 32 : from this image i want to read the pixels that are not white(clouds) and put them on another image.
the second image is the one i want to put the pixels over : this image size is also 512x512 but type jpg and Bith depth 24.
this class is what i'm using for reading and setting the pixels :
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Weather
{
public class RadarPixels
{
public RadarPixels(Bitmap image1, Bitmap image2)
{
ReadSetPixels(image1, image2);
}
private void ReadSetPixels(Bitmap image1 , Bitmap image2)
{
for(int x = 0; x < image1.Width; x++)
{
for(int y = 0; y < image1.Height; y++)
{
Color pixelColor = image1.GetPixel(x, y);
if (pixelColor != Color.FromArgb(255,255,255,255))
{
image2.SetPixel(x, y, pixelColor);
}
}
}
image2.Save(@"d:\mynewbmp.bmp");
image1.Dispose();
image2.Dispose();
}
}
}
using it in form1 :
public Form1()
{
InitializeComponent();
RadarPixels rp = new RadarPixels(
new Bitmap(Image.FromFile(@"D:\1.png")),
new Bitmap(Image.FromFile(@"D:\2.jpg")));
}
where 1.png is the image with the white background.
the result of the image mynewbmp.bmp is
not sure why it's have some white borders around the set pixels and if it copied all the pixels colors right.
if i'm changing the alpha of Color.FromArgb(255,255,255,255) from 255 to 1 or to 100 it will make mynewbmp.bmp same like 1.png only if the alpha is 255 it's getting closer to what i wanted.
Upvotes: 0
Views: 278
Reputation: 1124
As stated by others, some pixels are not pure white so they are included in the final image. Jtxkopt's solution to set a tolerance is great, but depending on the quality of the input images, and the smoothness of the edges, the tolarance may need to be constantly adjusted to get good results.
In the end, your solution and Jtxkopt's solution are both simulating a binary mask. That means you are compositing two images together based on a mask image, and that mask image only has 2 values, black and white. If a pixel is black draw image1, and if a pixel is white draw image2. Only in your case your 2 values are white and not-white. This is your theoretical binary mask image that has only black and white pixels:
Binary masks have very rough edges and are sometimes referred to as "cut out" masks because its like cutting things out with sizzors. When creating a binary mask the main issue is trying to determine which pixels to turn white, and which pixels to turn black, and the issue is almost always the edges of things. Jtxkopt's tolerance solution just helps make that determination and draw that final line. The user Andy suggested to use MakeTransparent but that has a similar result to a binary mask because you give it 1 color to decide what should be transparent and what should not (on or off).
Another way to composite two images is to use a grayscale mask. Instead of black and white you have many shades of gray to describe how to blend two images together. You get all the benefits of a binary mask if the pixels are pure white and pure black, but if the pixels are gray you get to blend the two images together based on their gray value. This creates a smoother composite. Here is a grayscale mask:
Here is a simple solution which takes each pixel, converts to a simple gray pixel, and uses that gray pixel to set the alpha channel (transparency) of image1 so that it can be composited on top of image2.
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Weather
{
public class RadarPixels
{
public RadarPixels(Bitmap image1, Bitmap image2)
{
ReadSetPixels(image1, image2);
}
private void ReadSetPixels(Bitmap image1 , Bitmap image2)
{
for(int x = 0; x < image1.Width; x++)
{
for(int y = 0; y < image1.Height; y++)
{
Color pixelColor = image1.GetPixel(x, y);
// just average R, G, and B values to get gray. Then invert by 255.
int invertedGrayValue = 255 - (int)((pixelColor.R + pixelColor.G + pixelColor.B) / 3);
// this keeps the original pixel color but sets the alpha value
image1.SetPixel(x, y, Color.FromArgb(invertedGrayValue, pixelColor));
}
}
// composite image1 on top of image2
using (Graphics g = Graphics.FromImage(image2))
{
g.CompositingMode = CompositingMode.SourceOver;
g.CompositingQuality = CompositingQuality.HighQuality;
g.DrawImage(image1, new Point(0, 0));
}
image2.Save(@"d:\mynewbmp.bmp");
image1.Dispose();
image2.Dispose();
}
}
}
The result:
This approach has pros and cons. Pros being that you don't have to adjust a tolerance value, and your resulting image isn't just pixels from image1 and some pixels from image2, the pixels are blended from both images. Con being that the clouds are slightly transparent and not as pronounced. That can be resolved by adjusting the contrast of the grayscale mask so that light areas are lighter and dark areas are darker. Or you can composite image1 onto image2 multiple times for "additive" effect. Or you can add another variable to decrease or increase the final alpha value.
I hope this alternative solution is useful.
-UPDATE FOR CLARITY-
To be more clear, in this solution we are inverting the gray pixel before using the value for the alpha channel. This is because alpha channel treats 0 as invisible and 255 as visible. So we want to invert white 255 to become black 0 by subtracting the pixel from 255. The end result is an inverted grayscale mask that looks like this:
At this point the closer something is to white the more it is visible in the composite. Since the clouds in this grayscale image are a little on the darker gray side, they will blend with the background image and lose a bit of vibrance. We would want to adjust this image so that the black stays black, but the grays become brighter to get that vibrance back.
With a simple change we can ignore all black 0 values because we want them to stay invisible, but increase the alpha of all other values. If we add:
if(invertedGrayValue > 0){ invertedGrayValue = 255; }
This will be just like a binary mask. All black values will remain invisible and all non-black are completely visible. But that defeats the purpose of using a grayscale mask. But we can set a "tolerance" value like this:
if(invertedGrayValue > tolerance){ invertedGrayValue = 255; }
This will combine the grayscale solution with the "tolerance" solution. If we set the tolerance to 64
for example, this means any gray value greater than 64
will be 100% visible, and anything below 64
will be invisible or slightly visible defined by its grayscale value. This means the white edges (which was the original problem) will not be compeletely removed, the will just be softly belended with the background. So this solution just "softens the edges".
Here is the result with tolerance of 64:
The final code would be:
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Weather
{
public class RadarPixels
{
public RadarPixels(Bitmap image1, Bitmap image2)
{
ReadSetPixels(image1, image2);
}
private void ReadSetPixels(Bitmap image1 , Bitmap image2)
{
int tolerance = 64;
for(int x = 0; x < image1.Width; x++)
{
for(int y = 0; y < image1.Height; y++)
{
Color pixelColor = image1.GetPixel(x, y);
// just average R, G, and B values to get gray. Then invert by 255.
int invertedGrayValue = 255 - (int)((pixelColor.R + pixelColor.G + pixelColor.B) / 3);
if(invertedGrayValue > tolerance){ invertedGrayValue = 255; }
// this keeps the original pixel color but sets the alpha value
image1.SetPixel(x, y, Color.FromArgb(invertedGrayValue, pixelColor));
}
}
// composite image1 on top of image2
using (Graphics g = Graphics.FromImage(image2))
{
g.CompositingMode = CompositingMode.SourceOver;
g.CompositingQuality = CompositingQuality.HighQuality;
g.DrawImage(image1, new Point(0, 0));
}
image2.Save(@"d:\mynewbmp.bmp");
image1.Dispose();
image2.Dispose();
}
}
}
Upvotes: 3
Reputation: 1160
As stated in the comments, the white pixels is not exactly white. You should therefore calculate the distance to the white color and check if the calculated distance is greater than some tolerance value.
To get the desired result, you should adjust the tolerance value below.
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Weather
{
public class RadarPixels
{
public RadarPixels(Bitmap image1, Bitmap image2)
{
ReadSetPixels(image1, image2);
}
private void ReadSetPixels(Bitmap image1 , Bitmap image2)
{
double tolerance = 46; // Don't forget adjust it.
for(int x = 0; x < image1.Width; x++)
{
for(int y = 0; y < image1.Height; y++)
{
Color pixelColor = image1.GetPixel(x, y);
if (GetDistance(pixelColor, Color.White) > tolerance)
{
image2.SetPixel(x, y, pixelColor);
}
}
}
image2.Save(@"d:\mynewbmp.bmp");
image1.Dispose();
image2.Dispose();
}
}
private double GetDistance(Color color1, Color color2)
{
double dr = color1.R - color2.R;
double dg = color1.G - color2.G;
double db = color1.B - color2.B;
return Math.Sqrt(dr * dr + dg * dg + db * db);
}
}
Upvotes: 2