C# Drawing.Imaging dropping GIF frames if they are identical - c#

I need to load GIF animations and convert them frame by frame to bitmaps. To do that, I am extracting my GIF file frame by frame using Drawing.Imaging library and then casting each frame to bitmap.
Everything works just fine except the times when the consecutive frames are the same, when there is no pixel difference. The library seems to be dropping such frames.
I came to that conslusion with a simple test. I have created an animation of a circle that is growing and shrinking with a pause between the moment the last circle dissapears and the new one is not yet shown. When I play the animation consisting of my extracted bitmaps that pause is not present. If I compare the GIFs of the same frame-length but different amounts of identical frames, the returned totalframescount value is different. I have also observed that webbrowsers display identical consecutive frames correctly.
public void DrawGif(Image img)
{
FrameDimension dimension = new FrameDimension(img.FrameDimensionsList[0]);
int frameCountTotal = img.GetFrameCount(dimension);
for (int framecount = 0; framecount < frameCountTotal; framecount++)
{
img.SelectActiveFrame(dimension, framecount);
Bitmap bmp = new Bitmap(img); //cast Image type to Bitmap
for (int i = 0; i < 16; i++)
{
for (int j = 0; j < 16; j++)
{
Color color = bmp.GetPixel(i, j);
DrawPixel(i, j, 0, color.R, color.G, color.B);
}
}
Does someone encountered such a problem?
As I am pretty new to C# - is there a way to modify .NET lib ?
Maybe there is a solution to my problem that I am not aware of, that does not involve changing the library?
Updated code - the result is the same
public void DrawGif(img)
{
int frameCountTotal = img.GetFrameCount(FrameDimension.Time);
for (int framecount = 0; framecount < frameCountTotal; framecount++)
{
img.SelectActiveFrame(FrameDimension.Time, framecount);
Bitmap bmp = new Bitmap(img);
for (int i = 0; i < 16; i++)
{
for (int j = 0; j < 16; j++)
{
Color color = bmp.GetPixel(i, j);
DrawPixel(i, j, 0, color.R, color.G, color.B);
}

The Image is not saving duplicate frames, so you have to take the time of each frame into account. Here is some sample code based on this book in Windows Programming on how to get all the frames and the correct duration. An example:
public class Gif
{
public static List<Frame> LoadAnimatedGif(string path)
{
//If path is not found, we should throw an IO exception
if (!File.Exists(path))
throw new IOException("File does not exist");
//Load the image
var img = Image.FromFile(path);
//Count the frames
var frameCount = img.GetFrameCount(FrameDimension.Time);
//If the image is not an animated gif, we should throw an
//argument exception
if (frameCount <= 1)
throw new ArgumentException("Image is not animated");
//List that will hold all the frames
var frames = new List<Frame>();
//Get the times stored in the gif
//PropertyTagFrameDelay ((PROPID) 0x5100) comes from gdiplusimaging.h
//More info on http://msdn.microsoft.com/en-us/library/windows/desktop/ms534416(v=vs.85).aspx
var times = img.GetPropertyItem(0x5100).Value;
//Convert the 4bit duration chunk into an int
for (int i = 0; i < frameCount; i++)
{
//convert 4 bit value to integer
var duration = BitConverter.ToInt32(times, 4*i);
//Add a new frame to our list of frames
frames.Add(
new Frame()
{
Image = new Bitmap(img),
Duration = duration
});
//Set the write frame before we save it
img.SelectActiveFrame(FrameDimension.Time, i);
}
//Dispose the image when we're done
img.Dispose();
return frames;
}
}
We need a structure to save the Bitmap and duration for each Frame
//Class to store each frame
public class Frame
{
public Bitmap Image { get; set; }
public int Duration { get; set;}
}
The code will load a Bitmap, check whether it is a multi-frame animated GIF. It then loops though all the frames to construct a list of separate Frame objects that hold the bitmap and duration of each frame. Simple use:
var frameList = Gif.LoadAnimatedGif ("a.gif");
var i = 0;
foreach(var frame in frameList)
frame.Image.Save ("frame_" + i++ + ".png");

Related

Convert 32bpp to 8bpp UWP

I'm trying to draw InkCanvas to an 8bpp image but when I try to do so the image convert itself to 32bpp, the lower I got was 24bpp but not 8bpp. Anyone can help me out? The image I am giving as input is an 8bpp BMP image created with paint.
Image imgToEdit;
InkCanvas inkCanvas;
file = await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFileAsync(Ambiente.imgBlankFirma);
await file.CopyAsync(photoFolder, NomeFile, NameCollisionOption.ReplaceExisting);
file = await photoFolder.GetFileAsync(NomeFile);
imgToEdit = imgFirma;
inkCanvas = inkCanvasFirma;
if (inkCanvas.InkPresenter.StrokeContainer.GetStrokes().Count <= 0)
{
errore = true;
return;
}
var randomAccessStream = await file.OpenReadAsync();
CanvasDevice device = CanvasDevice.GetSharedDevice();
CanvasRenderTarget renderTarget = new CanvasRenderTarget(device, (int)inkCanvas.ActualWidth, (int)inkCanvas.ActualHeight, 96); //inkCanvas.ActualWidth inkCanvas.ActualHeight
using (var ds = renderTarget.CreateDrawingSession())
{
var image = await CanvasBitmap.LoadAsync(device, randomAccessStream);
// draw your image first
ds.DrawImage(image);
// then draw contents of your ink canvas over it
ds.DrawInk(inkCanvas.InkPresenter.StrokeContainer.GetStrokes());
}
randomAccessStream.Dispose();
// save results
using (var fileStream = await file.OpenAsync(FileAccessMode.ReadWrite))
{
await renderTarget.SaveAsync(fileStream, CanvasBitmapFileFormat.Tiff, 1f);
}
This shows how to do the conversion by hand. It requires direct pointer access to the image data. So just create a new 8bpp (i.e. Format8bppIndexed) bitmap with the correct size for the conversion target. So converting the data should look something like this:
public static unsafe void Bgr24ToMono8(byte* source, byte* target, int sourceStride, int targetStride, int width, int height)
{
for(var y = 0; y < height; y++)
{
var sourceRow = source + y * sourceStride;
var targetRow = y * targetStride;
for (int x = 0; x < width; x++)
{
var sourceIndex = (sourceRow + x * 3);
var value = (byte)(sourceIndex[0] * 0.11f + sourceIndex[1] * 0.59f + sourceIndex[2] * 0.3f);
target[targetRow + x] = value;
}
}
}
if you have 32bpp data it should just be a issue of changing x * 3 to x * 4. Note that there is some confusion regarding Bgr vs Rbg, different contexts uses different terms for the same thing.
Note that this converts the bitmap to 8bpp grayscale. If you need 8bpp color indexed this will be much more work since you would ideally need to find a optimal color-map, find the closest colors in said map, and apply dithering to avoid banding. For this I would recommend some image processing library. I do not think there is any built in functions for this, and it is way to much work to demonstrate here.

Show an array of bytes as an image on a form

I wrote some code to show an array of bytes as an image. There is an array of bytes in which every element represents a value of 8-bit gray scale image. Zero equals the most black and 255 does the most white pixel. My goal is to convert this w*w-pixel gray-scale image to some thing accepted by pictureBox1.Image.
This is my code:
namespace ShowRawImage
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
int i = 0, j = 0, w = 256;
byte[] rawIm = new byte[256 * 256];
for(i = 0; i < w; ++i)
{
for (j = 0; j < w; ++j)
{
rawIm[i * w + j] = (byte)j; // BitConverter.GetBytes(j);
}
}
MemoryStream mStream = new MemoryStream();
mStream.Write(rawIm, 0, Convert.ToInt32(rawIm.Length));
Bitmap bm = new Bitmap(mStream, false);// the error occurs here
mStream.Dispose();
pictureBox1.Image = bm;
}
}
}
However I get this error:
Parameter is not valid.
The error snapshot
where is my mistake?
EDIT:
In next step I am going to display 16-bit grayscale images.
The Bitmap(Stream, bool) constructor expects a stream with an actual image format (eg. PNG, GIF, etc.) along with header, palette, and possibly compressed image data.
To create a Bitmap from raw data, you need to use the Bitmap(int width, int height, int stride, PixelFormat format, IntPtr scan0) constructor, but that is also quite inconvenient because you need a pinned raw data that you can pass as scan0.
The best if you just create an 8bpp bitmap with grayscale palette and set the pixels manually:
var bmp = new Bitmap(256, 256, PixelFormat.Format8bppIndexed);
// making it grayscale
var palette = bmp.Palette;
for (int i = 0; i < 255; i++)
palette.Entries[i] = Color.FromArgb(i, i, i);
bmp.Palette = palette;
Now you can access its raw content as bytes where 0 is black and 255 is white:
var bitmapData = bmp.LockBits(new Rectangle(Point.Empty, bmp.Size), ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
for (int y = 0; y < bitmapData.Height; y++)
{
for (int x = 0; x < bitmapData.Width; x++)
{
unsafe
{
((byte*) bitmapData.Scan0)[y * bitmapData.Stride + x] = (byte)x;
}
}
}
bmp.UnlockBits(bitmapData);
The result image:
But if you don't want to use unsafe code, or you want to set pixels by colors, you can use this library (disclaimer: written by me) that supports efficient manipulation regardless of the actual PixelFormat. Using that library the last block can be rewritten like this:
using (IWritableBitmapData bitmapData = bmp.GetWritableBitmapData())
{
IWritableBitmapDataRow row = bitmapData.FirstRow;
do
{
for (int x = 0; x < bitmapData.Width; x++)
row[x] = Color32.FromGray((byte)x); // this works for any pixel format
// row.SetColorIndex(x, x); // for the grayscale 8bpp bitmap created above
} while (row.MoveNextRow());
}
Or like this, using Parallel.For (this works only because in your example all rows are the same so the image is a horizontal gradient):
using (IWritableBitmapData bitmapData = bmp.GetWritableBitmapData())
{
Parallel.For(0, bitmapData.Height, y =>
{
var row = bitmapData[y];
for (int x = 0; x < bitmapData.Width; x++)
row[x] = Color32.FromGray((byte)x); // this works for any pixel format
// row.SetColorIndex(x, x); // for the grayscale 8bpp bitmap created above
});
}
As said in the comments - bitmap is not just an array. So to reach your goal you can create bitmap of needed size and set pixels with Bitmap.SetPixel:
Bitmap bm = new Bitmap(w, w);
for(var i = 0; i < w; ++i)
{
for (var j = 0; j < w; ++j)
{
bm.SetPixel(i,j, Color.FromArgb(j, j, j));
}
}

WPF Display a portion of bigger image in ImageBox

I'm coming from Winforms trying to rewrite a program in WPF, and I want to display a certain portion of the whole image, depending on an Id I use for a list, that I load each portion of the whole image in. I was able to do it successfully in Winforms, but I want to perform the same task in WPF using Controls.Image. Heres what I did in Winforms.
PictureBox picBox;
List<Image> tileImageList;
Image FullImage;
public TileFrame(PictureBox pbox)
{
picBox = pbox;
FullImage = picBox.Image; //The source of the picBox is set to the full image on init
tileImageList = new List<Image>();
PopTileList();
}
void PopTileList()
{
const int SIZE = 32;
Bitmap bitFullImage = new Bitmap(FullImage);
for (int y = 0; y < 48; y++)
{
for (int x = 0; x < 64; x++)
{
var portion = bitFullImage.Clone(new Rectangle((x * SIZE), (y * SIZE), SIZE, SIZE), bitFullImage.PixelFormat);
tileImageList.Add(portion);
}
}
picBox.Image = tileImageList[10];//The first image that shows when this is done
}
public void ShowTilePic(int selectedId)
{
picBox.Image = tileImageList[--selectedId];
}
Since the image being displayed will change based on the selected item of a listbox, the tileImageList is crucial for relating the list box selected index and the tileImageList index. Other answers I've searched for seemed to make it much more complicated than what I've done here. Is there a simple way to do this in WPF and in code?
Nvm I figured it out.
List<CroppedBitmap> tileImageList;
Image imageBox;
public TileFrame(MainWindow mWindow)
{
tileImageList = new List<CroppedBitmap>();
imageBox = mWindow.ImageBox;
PopTileList();
}
void PopTileList()
{
const int SIZE = 32;
var bitmapImage = (BitmapSource)imageBox.Source;
for (int y = 0; y < 48; y++)
{
for (int x = 0; x < 64; x++)
{
var portion = new CroppedBitmap(bitmapImage, new Int32Rect((x * SIZE), (y * SIZE), SIZE, SIZE));
tileImageList.Add(portion);
}
}
}
public void ShowTilePic(int selectedId)
{
imageBox.Source = tileImageList[selectedId];
}

Will using DirectX or OpenGL speed up 2D Rendering on a windows form?

I'm working on a structured light project where I need to project what are essentially barcodes at 60fps. These codes are built from bitmap images that are 1920x1200. When using GDI in C# I'm only getting around 19fps on a pretty beastly computer. I'm looking into SharpDX now and before investing a lot of time I was wondering if rendering the images to the screen via DirectX or OpenGL will actually be faster?
Sample code:
// Gets a reference to the current BufferedGraphicsContext
currentContext = BufferedGraphicsManager.Current;
List<Image> imagesGrayCode = new List<Image>();
Bitmap bitmap = (Bitmap)Image.FromFile("F:/LAB/Hardware Triggering Demo/Lib/Patterns_11bit_RLL.tiff");
int count = bitmap.GetFrameCount(FrameDimension.Page);
for (int idx = 0; idx < count; idx++)
{
// save each frame to a bytestream
bitmap.SelectActiveFrame(FrameDimension.Page, idx);
MemoryStream byteStream = new MemoryStream();
bitmap.Save(byteStream, ImageFormat.Tiff);
// and then create a new Image from it
imagesGrayCode.Add(Image.FromStream(byteStream));
//this.progressBar1.Value = idx;
}
Thread.Sleep(1000);
stopwatch = Stopwatch.StartNew();
int acq_wait_time = 10;
for (int i = 0; i < 10; i++)
{
foreach (Image img in imagesGrayCode)
{
myBuffer.Graphics.DrawImage(img, this.DisplayRectangle);
// Renders the contents of the buffer to the drawing surface associated with the buffer.
myBuffer.Render();
// Renders the contents of the buffer to the specified drawing surface.
myBuffer.Render(this.CreateGraphics());
//System.Threading.Thread.Sleep(acq_wait_time);
stopwatch.Reset();
stopwatch.Start();
while (stopwatch.ElapsedMilliseconds < acq_wait_time)
{ }
FPS++;
Application.DoEvents();
}
}
Updated Code:
// Gets a reference to the current BufferedGraphicsContext
currentContext = BufferedGraphicsManager.Current;
List<Image> imagesGrayCode = new List<Image>();
List<BufferedGraphics> ImageBuffers = new List<BufferedGraphics>();
Bitmap bitmap = (Bitmap)Image.FromFile("F:/LAB/Hardware Triggering Demo/Lib/Patterns_11bit_RLL.tiff");
int count = bitmap.GetFrameCount(FrameDimension.Page);
for (int idx = 0; idx < count; idx++)
{
// save each frame to a bytestream
bitmap.SelectActiveFrame(FrameDimension.Page, idx);
MemoryStream byteStream = new MemoryStream();
bitmap.Save(byteStream, ImageFormat.Tiff);
// and then create a new Image from it
imagesGrayCode.Add(Image.FromStream(byteStream));
//this.progressBar1.Value = idx;
}
//create a buffer from each image in memory
for (int i = 0; i < imagesGrayCode.Count(); i++)
{
ImageBuffers.Add(currentContext.Allocate(this.CreateGraphics(),this.DisplayRectangle));
ImageBuffers.ElementAt(i).Graphics.DrawImage(imagesGrayCode.ElementAt(i), this.DisplayRectangle);
ImageBuffers.ElementAt(i).Render();
}
Thread.Sleep(1000);
stopwatch = Stopwatch.StartNew();
int acq_wait_time = 10;
for (int x = 0; x < 10; x++)
{
//display image buffers sequentially
for (int i = 0; i < imagesGrayCode.Count(); i++)
{
// Renders the contents of the buffer to the specified drawing surface.
ImageBuffers.ElementAt(i).Render(this.CreateGraphics());
//System.Threading.Thread.Sleep(acq_wait_time);
stopwatch.Reset();
stopwatch.Start();
while (stopwatch.ElapsedMilliseconds < acq_wait_time)
{ }
FPS++;
Application.DoEvents();
}
}
You're drawing > rendering > drawing > rendering > drawing > rendering etc. You can do all the drawing ahead of time so you only need to render on each frame. Use more of whatever myBuffer is; one for each image.
Profiler tools are awesome for these problems. I'm guessing based on your partial code and my experience, but a profiler can tell you exactly how long each function takes to complete.

Faces not getting detected from Kinect Feed

Below is the code for a method which I am using for detecting faces from Kinect Feed and then setting the pixels from the face into a new image. . It is triggered by a Gesture which is done by GestureFlag . The Detect method which I am calling from the FaceDetection class is taken from the EMGU CV sample.
public string Detect(WriteableBitmap colorBitmap, int GestureFlag)
{
if(GestureFlag!=0)
{
List<Rectangle> faces = new List<Rectangle>();
Bitmap bitface = BitmapFromSource(colorBitmap);
Image<Bgr, Byte> image = new Image<Bgr, Byte>(bitface);
FaceDetection.Detect(image, "haarcascade_frontalface_default.xml",faces);
Bitmap img = new Bitmap(#"C:\Users\rama\Downloads\kinect-2-background-removal-master\KinectBackgroundRemoval\Assets\emptyimage.png");
img = ResizeImage(img, 1540, 1980);
int high = image.Height;
int width = image.Width;
for (int i = 0; i < width; i++)
{
for (int j = 0; j < high; j++)
{
Bgr pixel = image[j,i];
System.Drawing.Point p = new System.Drawing.Point(j,i);
if (faces[0].Contains(p) && i<1540 && j<1980)
{
img.SetPixel(i, j, System.Drawing.Color.FromArgb(255, (int)pixel.Blue, (int)pixel.Green,(int) pixel.Red));
}
}
}
count++;
key = count.ToString() + "rich.jpg";
image.Save(#"C:\Users\rama\Downloads\kinect-2-background-removal-master\KinectBackgroundRemoval\Assets\"+key);
img.Save(#"C:\Users\rama\Downloads\kinect-2-background-removal-master\KinectBackgroundRemoval\"+key);
bool status = UploadToS3("nitish2", key, #"C:\Users\rama\Downloads\kinect-2-background-removal-master\KinectBackgroundRemoval\"+key);
var FBClient = new FacebookClient();
var UploadToFacebook = new FacebookUpload(FBClient, key);
UploadToFacebook.Show();
GestureFlag = 0;
}
return key;
}
The problem I'm running into is that an entirely different set of pixels gets printed on the new image which I'm saving.
Basically i think that the problem is here:
FaceDetection.Detect(image, "haarcascade_frontalface_default.xml",faces);
for (int i = 0; i < width; i++)
{
for (int j = 0; j < high; j++)
{
Bgr pixel = image[j,i];
System.Drawing.Point p = new System.Drawing.Point(j,i);
if (faces[0].Contains(p) && i<1540 && j<1980)
{
img.SetPixel(i, j, System.Drawing.Color.FromArgb(255, (int)pixel.Blue, (int)pixel.Green,(int) pixel.Red));
}
}
}
So can someone please point out where I'm going wrong?
Thank you very much.
EDITS : I've tried putting flags like faces[0]!=null which should take care of whether FaceDetection.Detect is actually returning anything but still I'm getting the same result.
I've also tried saving the ColorBitmap and testing it against the EMGU CV sample and it detects the faces in the image easily.
EDIT 2: So I've cross checked the co-ordinates of the rectangle which is being printed with the co-ordinates of the face being detected and the values of the Rectangle() being populated. They turn out to be almost same as far as I can see. So no luck there.
I don't know what else I can try for debugging.
If someone could point that out that would be great.
Thanks!
Ok I got it to work finally.
The problem was the Point[j,i] which should have been Point[i,j] . Also my FaceDetection method proved to be a little erratic and I had to fix that too to finally properly debug my code.

Categories