How can I remove transparency from an animated GIF? - c#

I can't believe this hasn't been asked yet, but...
How can I replace the transparent background of a GIF image to white, without breaking the animation?
I am way out of my field here, so I'm pretty much lost (I really don't know what to do).
This is what I tried without luck:
public byte[] RemoveTransparency(byte[] gifBytes)
{
using(MemoryStream gifStream = new MemoryStream(gifBytes))
using(Image gifImage = Image.FromStream(gifStream))
using(Image newImage = new Bitmap(gifImage.Width, gifImage.Height, PixelFormat.Format32bppPArgb))
using(MemoryStream newImageStream = new MemoryStream())
{
Color background = Color.White;
newImage.SetResolution(gifImage.HorizontalResolution, gifImage.VerticalResolution);
using (Graphics graphics = Graphics.FromImage(newImage))
{
graphics.Clear(background );
graphics.DrawImage(gifImage, 0, 0, gifImage.Width, gifImage.Height);
}
newImage.Save(newImageStream);
return newImageStream.ToArray();
}
}

You can use a bitmap.
using(var ms = new MemoryStream(gifBytes))
using(var b = new Bitmap(Image.FromStream(ms)))
{
for(var y = 0; y < b.Height; ++y)
for(var x = 0; x < b.Width; ++x)
if(b.GetPixel(x, y).A == 0) //Completely opaque
b.SetPixel(x, y, Color.White); //Sets white
}
//Note: b goes out of scope here and will be deleted eventually.
Do note that this algorithm is extremely slow on large images, a typical approach to get around this is to access the internal buffer directly which requires unsafe context. See MSDN Bitmap reference or this: https://msdn.microsoft.com/en-us/library/5ey6h79d(v=vs.110).aspx
If the bitmap breaks the animation, load each frame as described in the comments of your post and then write the final buffer back into main image memory. Comment if you want me to edit for this context.

Related

fast way to split 8bit bitmap into eight differet 1bit bitmaps

I need a way to convert 1000+ 8bit bitmaps into eight 1bit bitmaps.
Currently I am running two loops which read each pixel from the main image and assign it a 1bpp image. It takes a very long time to accomplish this, anyway better to do it?
Here is an example of my code (separates only into two images):
Bitmap rawBMP = new Bitmap(path);
Bitmap supportRAW = new Bitmap(rawBMP.Width, rawBMP.Height);
Bitmap modelRAW = new Bitmap(rawBMP.Width, rawBMP.Height);
Color color = new Color();
for (int x = 0; x < rawBMP.Width; x++)
{
for (int y = 0; y < rawBMP.Height; y++)
{
color = rawBMP.GetPixel(x, y);
if (color.R == 166) //model
{
modelRAW.SetPixel(x, y, Color.White);
}
if (color.R == 249) //Support
{
supportRAW.SetPixel(x, y, Color.White);
}
}
}
var supportBMP = supportRAW.Clone(new Rectangle(0, 0, rawBMP.Width, rawBMP.Height), System.Drawing.Imaging.PixelFormat.Format1bppIndexed);
var modelBMP = modelRAW.Clone(new Rectangle(0, 0, rawBMP.Width, rawBMP.Height), System.Drawing.Imaging.PixelFormat.Format1bppIndexed);
If you have to check every pixel then you are going to have to loop through them all at least once, however like TaW suggest there are more efficient ways to access pixels.
SetPixel and GetPixel are much slower then you accessing the data directly, Look at the use of unsafe to get direct access to the data, or marshaling to copy the data back and forth.
( see https://stackoverflow.com/a/1563170 for a more detailed written by notJim)

c# drawing objects at once instead of using a loop

Hello i am trying to draw about 1000 images and between 10 to 100 rectangels and elipses.
But i need all of them to show up on screen only when they done loading(not in a loading screen but in a game or slideshow). so for example
texturegrass = MyApp.Properties.Resources.Grass
Rectangle[] rects;
recs = new Rectangle[1000]
for (int i = 0; i < rects.Length; i++)
{
g.DrawImage(texturegrass,rects[i]);
}
this is what i done so far but every rectangle is been drawn by it own what cause a flickering problam.
I have double bufferd the app.
I tried using parallel but the application keep crashing
I hope one of you guys can help me...
##*
You can use a Graphics object to create the image off-screen, and then draw the image on the screen using the screen's Graphics object.
var bmp = new Bitmap(MyWidth, CMyHeight);
var gOff = Graphics.FromImage(bmp);
gOff.FillRectangle(new SolidBrush(Color.White), 0, 0, bmp.Width, bmp.Height);
texturegrass = MyApp.Properties.Resources.Grass
Rectangle[] rects = ...;
recs = new Rectangle[1000]
for (int i = 0; i < rects.Length; i++) {
gOff.DrawImage(texturegrass,rects[i]);
}
At this point you can draw bmp all at once on the screen's Graphic.
Microsoft: How to Draw Images Off-Screen

(Bitmap)image behaves differently than new Bitmap(image)

Here is the test i wrote and that will currently fail:
var unusableColor = Color.FromArgb(13, 19, 20, 19);
var retrievedColor = Color.Empty;
var tempFile = Path.GetTempFileName();
using (var bitmap = new Bitmap(1, 1))
{
bitmap.SetPixel(0, 0, unusableColor);
bitmap.Save(tempFile, ImageFormat.Png);
}
using (var image = Image.FromFile(tempFile))
// This will lead to the error
using (var bitmap = new Bitmap(image))
// But this will work
//using (var bitmap = (Bitmap)image)
{
retrievedColor = bitmap.GetPixel(0, 0);
}
Assert.That(retrievedColor, Is.SameAs(unusableColor));
If you take a look into the retrievedColor you'll find that it will be the same as Color.FromArgb(13, 19, 19, 19). So the difference will be that the green part has changed from 20 to 19.
Any idea why this happens or under which circumstances the constructor of the Bitmap will change a pixel?
Update
Seems to be a deeper nested problem. By replacing the Bitmap constructor by a simple cast of the image variable the problem goes away. This maybe solves the problem, but it doesn't explain it. Further more i was able to reproduce the problem even in Paint.Net by the following procedure:
Open Paint.Net and create a new image (size doesn't matter)
Select all (Ctrl+A)
Remove the selection (Del)
Open the color dialog (F8)
Enter the above values for RGB (19, 20, 19) and at the bottom the transparency (13).
Select the fill tool (F)
Fill the color into the empty image
Select the color selection tool (K)
Click somewhere into your fresh image and watch the color dialog
So it seems it is maybe a deeper problem, not caused by the Bitmap or Image class but maybe by some deeper functionality like GDI+ or something similar.
Update 2
I just wrote a new test to find out all affected colors:
for (int a = 0; a < 256; a++)
{
for (int r = 0; r < 256; r++)
{
for (int g = 0; g < 256; g++)
{
for (int b = 0; b < 256; b++)
{
using (var bitmap = new Bitmap(1, 1))
{
var desiredColor = Color.FromArgb(a, r, g, b);
bitmap.SetPixel(0, 0, desiredColor);
// This will fail in a lot of colors with a low alpha channel value
using (var copiedBitmap = new Bitmap(bitmap))
// This will work, cause the information is entirely copied.
//using (var copiedBitmap = (Bitmap)bitmap.Clone())
{
var retrievedColor = copiedBitmap.GetPixel(0, 0);
if (desiredColor != retrievedColor)
{
Debug.Print(desiredColor + " != " + retrievedColor);
}
}
}
}
}
}
Please don't let it run completely on itself, cause it will take a loonng time to finish and it also finds a looots of differences. But what you can see, if you play around with the transparency (setting to 1 or 10) then you'll see that the RGB values use this as some kind of bit depth.
So the problem occurs if you create a new Bitmap from an existing one that uses low transparency values. The real root cause seems to be far down in GDI, Kernel or somewhere in this area and can't be solved from .Net.
Simply be aware that a color can change by calling the bitmap constructor if the color has a low transparency value. If you really need the original colors to stay alive in a second instance instead use (Bitmap)myBitmap.Clone() or if you load it from disk use (Bitmap)Image.FromFile(filename) cause Image is only an abstract class which will normally instantiated through the Bitmap class.
I checked PNG file saved with your code using Paint.NET and pixel color is exactly unusableColor.
If you change your reading code with this:
using (Bitmap bitmap = (Bitmap)Image.FromFile(tempFile))
{
retrievedColor = bitmap.GetPixel(0, 0);
}
everything works
you can use Clone method:
using (var image = Image.FromFile(tempFile))
{
using (var bitmap = image.Clone() as Bitmap)
{
retrievedColor = bitmap.GetPixel(0, 0);
}
}
Problem is in 'new Bitmap(image)' because it creates new instance. If you look into bitmap's constructor, it creates new transparent image and draws source image. graphics object has smoothing mode property, which is used for drawing quality. default is no antialiasing.
Here is the Bitmap's constructor:
Graphics graphics = null;
try
{
graphics = Graphics.FromImage(this);
graphics.Clear(Color.Transparent);
graphics.DrawImage(original, 0, 0, width, height);
}
finally
{
if (graphics != null)
{
graphics.Dispose();
}
}
So if you just load image from file, or clone, bitmap data is same.

Fill the holes in emgu cv

How can I fill the holes in binary image in emgu cv?
In Aforge.net it's easy, use Fillholes class.
Thought the question is a little bit old, I'd like to contribute an alternative solution to the problem.
You can obtain the same result as Chris' without memory problem if you use the following:
private Image<Gray,byte> FillHoles(Image<Gray,byte> image)
{
var resultImage = image.CopyBlank();
Gray gray = new Gray(255);
using (var mem = new MemStorage())
{
for (var contour = image.FindContours(
CHAIN_APPROX_METHOD.CV_CHAIN_APPROX_SIMPLE,
RETR_TYPE.CV_RETR_CCOMP,
mem); contour!= null; contour = contour.HNext)
{
resultImage.Draw(contour, gray, -1);
}
}
return resultImage;
}
The good thing about the method above is that you can selectively fill holes that meets your criteria. For example, you may want to fill holes whose pixel count (count of black pixels inside the blob) is below 50, etc.
private Image<Gray,byte> FillHoles(Image<Gray,byte> image, int minArea, int maxArea)
{
var resultImage = image.CopyBlank();
Gray gray = new Gray(255);
using (var mem = new MemStorage())
{
for (var contour = image.FindContours(
CHAIN_APPROX_METHOD.CV_CHAIN_APPROX_SIMPLE,
RETR_TYPE.CV_RETR_CCOMP,
mem); contour!= null; contour = contour.HNext)
{
if ( (contour.Area < maxArea) && (contour.Area > minArea) )
resultImage.Draw(contour, gray, -1);
}
}
return resultImage;
}
Yes there is a method but it's a bit messy as its based on cvFloodFill operation. Now all this algorithm is designed to do is fill an area with a colour until it reaches an edge similar to a region growing algorithm. To use this effectively you need to use a little inventive coding but I warn you this code is only to get you started it may require re-factoring to speed things up . As it stands the loop goes through each of your pixels that are less then 255 applies cvFloodFill checks what size the area is and then if it is under a certain area fill it in.
It is important to note that a copy of the image is made of the original image to be supplied to the cvFloodFill operation as a pointer is used. If the direct image is supplied then you will end up with a white image.
OpenFileDialog OpenFile = new OpenFileDialog();
if (OpenFileDialog.ShowDialog() == DialogResult.OK)
{
Image<Bgr, byte> image = new Image<Bgr, byte>(OpenFile.FileName);
for (int i = 0; i < image.Width; i++)
{
for (int j = 0; j < image.Height; j++)
{
if (image.Data[j, i, 0] != 255)
{
Image<Bgr, byte> image_copy = image.Copy();
Image<Gray, byte> mask = new Image<Gray, byte>(image.Width + 2, image.Height + 2);
MCvConnectedComp comp = new MCvConnectedComp();
Point point1 = new Point(i, j);
//CvInvoke.cvFloodFill(
CvInvoke.cvFloodFill(image_copy.Ptr, point1, new MCvScalar(255, 255, 255, 255),
new MCvScalar(0, 0, 0),
new MCvScalar(0, 0, 0), out comp,
Emgu.CV.CvEnum.CONNECTIVITY.EIGHT_CONNECTED,
Emgu.CV.CvEnum.FLOODFILL_FLAG.DEFAULT, mask.Ptr);
if (comp.area < 10000)
{
image = image_copy.Copy();
}
}
}
}
}
The "new MCvScalar(0, 0, 0), new MCvScalar(0, 0, 0)," are not really important in this case as you are only filling in results of a binary image. YOu could play around with other settings to see what results you can achieve. "if (comp.area < 10000)" is the key constant to change is you want to change what size hole the method will fill.
These are the results that you can expect:
Original
Results
The problem with this method is it's extremely memory intensive and it managed to eat up 6GB of ram on a 200x200 image and when I tried 200x300 it ate all 8GB of my RAM and brought everything to a crashing halt. Unless a majority of your image is white and you want to fill in tiny gaps or you can minimise where you apply the method I would avoid it. I would suggest writing you own class to examine each pixel that is not 255 and add the number of pixels surrounding it. You can then record the position of each pixel that was not 255 (in a simple list) and if your count was bellow a threshold set these positions to 255 in your images (by iterating though the list).
I would stick with the Aforge FillHoles class if you do not wish to write your own as it is designed for this purpose.
Cheers
Chris
you can use FillConvexPoly
image.FillConvexPoly(externalContours.ToArray(), new Gray(255));

How do I superimpose one bitmap image on another in GDI+?

Using GDI+ I've made a heatmap bmp and I'd like to superimpose it on top of my bmp map. I've saved the two bmps to disk, and they look good, I just need a way to put them together. Is there any way to do this, perhaps using the Graphics object? How is transparency/alpa involved?
I'm very new to GDI programming so please be as specific as possible.
OK - here's an answer. At some point I need to learn how GDI+ works...
I couldn't get around the transarency issues, but this works. It just copies the non-white pixels from the overlay to the map:
for (int x = 0; x < map.Width; x++)
for (int y = 0; y < map.Height; y++) {
Color c = overlay.GetPixel(x, y);
if ((c.A != 255) || (c.B != 255) || (c.G != 255) || (c.R != 255))
map.SetPixel(x, y, c);
This should do the job...
At the moment the Image you want to superimpose onto the main image will be located in the top left corner of the main Image, hence the new Point(0,0). However you could change this to locate the image anywhere you want.
void SuperimposeImage()
{
//load both images
Image mainImage = Bitmap.FromFile("PathOfImageGoesHere");
Image imposeImage = Bitmap.FromFile("PathOfImageGoesHere");
//create graphics from main image
using (Graphics g = Graphics.FromImage(mainImage))
{
//draw other image on top of main Image
g.DrawImage(imposeImage, new Point(0, 0));
//save new image
mainImage.Save("OutputFileName");
}
}

Categories

Resources