Icon size changed when using BitmapDecoder.Create in WPF - c#

We have a method to Convert icon to given size, which likes below:
private BitmapFrame GetSizedSource(Icon icon, int size)
{
var stream = IconToStream(icon);
var decoder = BitmapDecoder.Create(stream, BitmapCreateOptions.DelayCreation, BitmapCacheOption.OnDemand);
var frame = decoder.Frames.SingleOrDefault(_ => Math.Abs(_.Width - size) < double.Epsilon);
return frame;
}
private Stream IconToStream(Icon icon)
{
using (var stream = new MemoryStream())
{
icon.Save(stream);
stream.Position = 0;
return stream;
}
}
As we pass the icon, which height/width is 32, and parameter size is 32.
Actually, the decoder.Frame[0] width/height is 1.0, I don't know why?
Did I miss something?

Problem is in IconToStream which creates MemoryStream, copies icon into it, returns reference and then disposes all resources allocated by MemoryStream which effectively makes your stream and therefore Frame empty. If you would change GetSizedSource to something like below that returns BitmapFrame before desposing MemoryStream is should work:
private BitmapFrame GetSizedSource(Icon icon, int size)
{
using (var stream = new MemoryStream())
{
icon.Save(stream);
stream.Position = 0;
return BitmapDecoder.Create(stream, BitmapCreateOptions.DelayCreation, BitmapCacheOption.OnDemand)
.Frames
.SingleOrDefault(_ => Math.Abs(_.Width - size) < double.Epsilon);
}
}

Related

How to remove frames from a video file recorded with Aforge

I have to implement via software a CCTV circuit with AForge.
I can write a video file with
private VideoFileWriter FileWriter = new VideoFileWriter();
and write a new frame with
private void LocalWebCam_NewFrame(object sender, AForge.Video.NewFrameEventArgs eventArgs)
{
try
{
Image img = (Bitmap)eventArgs.Frame.Clone();
MemoryStream ms = new MemoryStream();
img.Save(ms, ImageFormat.Bmp);
ms.Seek(0, SeekOrigin.Begin);
bi = new BitmapImage();
bi.BeginInit();
bi.StreamSource = ms;
bi.EndInit();
bi.Freeze();
Dispatcher.BeginInvoke(new ThreadStart(delegate
{ frameHolder.Source = bi; }));
FileWriter.WriteVideoFrame(BitmapImage2Bitmap(bi));
}
catch (Exception ex)
{}
}
So the file writing by frame adding is fine.
So in order not to fill the hd with huge files I decide for example that when the avi file reaches 10MBs it is split to a new file
1.avi is renamed to 2.avi and a new file is started
1.avi --> 2.avi
then
1.avi --> 2.avi --> 3.avi
and so on. Eventually when I will have 1.avi 2.avi... 10.avi 11.avi I will delete 11.avi
so to always have 10 file. A rolling system.
And that works but is not smart all.
What would be great instead is that of file dividing when reaching a certain size I continue adding the new frames at the end AND I remove the new frames at the beginning so that the whole avi file size does not exceeds a certain size and I ALWAYS have the last minutes of registration
I am not sure if such a functionality exists. But you can try a different approach of storing the Bitmap frames as a buffer, and making a VideoFileWriter whenever you need it.
You could make a class to store the frames as an array of Bitmap, and then make it as a video whenever you want to. Here is a simple class to do the same.
public class CustomVideoBuffer
{
public readonly int BufferSize;
protected readonly Bitmap[] Buffer;
protected int currentFrame;
public CustomVideoBuffer(int bufferSize = 100)
{
BufferSize = bufferSize;
Buffer = new Bitmap[BufferSize];
currentFrame = 0;
}
public void AddFrame(Bitmap image)
{
Buffer[currentFrame++] = image;
currentFrame %= BufferSize;
}
public VideoFileWriter GetVideo()
{
var FileWriter = new VideoFileWriter(); // Create an instance here
int frame = currentFrame;
do
{
var img = Buffer[++frame];
if (img!=null)
FileWriter.WriteVideoFrame(img);
frame %= BufferSize;
} while (frame != currentFrame); // start from (currentFrame+1), and cycle all frames till currentFrame
return FileWriter;
}
}
Next, create an instance of this class in your program as
CustomVideoBuffer myBuffer = new CustomVideoBuffer(300); // Create a buffer of size 300
Then, add frames one by one as shown
private void LocalWebCam_NewFrame(object sender, AForge.Video.NewFrameEventArgs eventArgs)
{
try
{
// Get Bitmap converted from the eventArgs
Bitmap img = (Bitmap)eventArgs.Frame.Clone();
myBuffer.AddFrame(img); // Add the frame to the buffer
}
catch (Exception ex) { ... }
}
Finally, when you are done, just obtain the prepared VideoFileWriter() from your buffer with
VideoFileWriter FileWriter = myBuffer.GetVideo();

Generated gif has invalid header

I am trying to generate a gif using Bumpkit gif encoder and while the gif works (Except for the first frames acting out), when I try to load the gif in photoshop, it says "Could not complete request because the file-format module cannot parse the file".
I don't know how to check the validity of the gif because it works when I view it. This is how Im using the Bumpkit library:
public void SaveImagesAsGif(Stream stream, ICollection<Bitmap> images, float fps, bool loop)
{
if (images == null || images.ToArray().Length == 0)
{
throw new ArgumentException("There are no images to add to animation");
}
int loopCount = 0;
if (!loop)
{
loopCount = 1;
}
using (var encoder = new BumpKit.GifEncoder(stream, null, null, loopCount))
{
foreach (Bitmap bitmap in images)
{
encoder.AddFrame(bitmap, 0, 0, TimeSpan.FromSeconds(1 / fps));
}
}
stream.Position = 0;
}
Am I doing something wrong when generating the gif?
When you are using the Bumpkit gif encoder library, I think you have to call the InitHeader function first.
Take from the GifEncoder.cs source:
private void InitHeader(Stream sourceGif, int w, int h)
You can see the source code for the InitHeader function, the AddFrame function and the rest of the GifEncoder.cs file at https://github.com/DataDink/Bumpkit/blob/master/BumpKit/BumpKit/GifEncoder.cs
So it's a small edit to your code:
public void SaveImagesAsGif(Stream stream, ICollection<Bitmap> images, float fps, bool loop)
{
if (images == null || images.ToArray().Length == 0)
{
throw new ArgumentException("There are no images to add to animation");
}
int loopCount = 0;
if (!loop)
{
loopCount = 1;
}
using (var encoder = new BumpKit.GifEncoder(stream, null, null, loopCount))
{
//calling initheader function
//TODO: Change YOURGIFWIDTHHERE and YOURGIFHEIGHTHERE to desired width and height for gif
encoder.InitHeader(stream, YOURGIFWIDTHHERE, YOURGIFHEIGHTHERE);
foreach (Bitmap bitmap in images)
{
encoder.AddFrame(bitmap, 0, 0, TimeSpan.FromSeconds(1 / fps));
}
}
stream.Position = 0;
}

Captured photo has vertical stripes

I'm capturing a photo using the WinRT Mediacapture class, but when I take the picture it gets weird transparent stripes, it's kind of hard to explain so here's some pictures:
Before capturing picture (Previewing)
After taking picture
I have seen other people with kind of the same problem around here (like here, but the solutions to them didn't seem to work for me. (either no result, or the photo got messed up)
Code I use for setting the resolution:
System.Collections.Generic.IEnumerable<VideoEncodingProperties> available_resolutions = captureManager.VideoDeviceController.GetAvailableMediaStreamProperties(MediaStreamType.Photo).Select(x => x as VideoEncodingProperties);
foreach (VideoEncodingProperties resolution in available_resolutions)
{
if (resolution != null && resolution.Width == 640 && resolution.Height == 480) //(resolution.Width==1920 && resolution.Height==1080) //resolution.Width==640 && resolution.Height==480)
{
await captureManager.VideoDeviceController.SetMediaStreamPropertiesAsync(MediaStreamType.Photo, resolution);
}
}
Code I'm using for taking the photo:
private async Task<BitmapImage> ByteArrayToBitmapImage(byte[] byteArray)
{
var bitmapImage = new BitmapImage();
using (var stream = new InMemoryRandomAccessStream())
{
await stream.WriteAsync(byteArray.AsBuffer());
stream.Seek(0);
await bitmapImage.SetSourceAsync(stream);
await stream.FlushAsync();
}
return bitmapImage;
}
/// <summary>
/// Relayed Execute method for TakePictureCommand.
/// </summary>
async void ExecuteTakePicture()
{
System.Diagnostics.Debug.WriteLine("Started making picture");
DateTime starttime = DateTime.Now;
ImageEncodingProperties format = ImageEncodingProperties.CreateJpeg();
using (var imageStream = new InMemoryRandomAccessStream())
{
await captureManager.CapturePhotoToStreamAsync(format, imageStream);
//Compresses the image if it exceedes the maximum file size
imageStream.Seek(0);
//Resize the image if needed
uint maxImageWidth = 640;
uint maxImageHeight = 480;
if (AvatarPhoto)
{
maxImageHeight = 200;
maxImageWidth = 200;
//Create a BitmapDecoder from the stream
BitmapDecoder resizeDecoder = await BitmapDecoder.CreateAsync(imageStream);
if (resizeDecoder.PixelWidth > maxImageWidth || resizeDecoder.PixelHeight > maxImageHeight)
{
//Resize the image if it exceedes the maximum width or height
WriteableBitmap tempBitmap = new WriteableBitmap((int)resizeDecoder.PixelWidth, (int)resizeDecoder.PixelHeight);
imageStream.Seek(0);
await tempBitmap.SetSourceAsync(imageStream);
WriteableBitmap resizedImage = tempBitmap.Resize((int)maxImageWidth, (int)maxImageHeight, WriteableBitmapExtensions.Interpolation.Bilinear);
tempBitmap = null;
//Assign to imageStream the resized WriteableBitmap
await resizedImage.ToStream(imageStream, BitmapEncoder.JpegEncoderId);
resizedImage = null;
}
//Converts the final image into a Base64 String
imageStream.Seek(0);
}
//Converts the final image into a Base64 String
imageStream.Seek(0);
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(imageStream);
PixelDataProvider pixels = await decoder.GetPixelDataAsync();
byte[] bytes = pixels.DetachPixelData();
//Encode image
InMemoryRandomAccessStream encoded = new InMemoryRandomAccessStream();
BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, encoded);
encoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Ignore, maxImageWidth, maxImageHeight, decoder.DpiX, decoder.DpiY, bytes);
//Rotate the image based on the orientation of the camera
if (currentOrientation == DisplayOrientations.Portrait)
{
encoder.BitmapTransform.Rotation = BitmapRotation.Clockwise90Degrees;
}
else if (currentOrientation == DisplayOrientations.LandscapeFlipped)
{
encoder.BitmapTransform.Rotation = BitmapRotation.Clockwise180Degrees;
}
if (FrontCam)
{
if (currentOrientation == DisplayOrientations.Portrait)
{
encoder.BitmapTransform.Rotation = BitmapRotation.Clockwise270Degrees;
}
else if (currentOrientation == DisplayOrientations.LandscapeFlipped)
{
encoder.BitmapTransform.Rotation = BitmapRotation.Clockwise180Degrees;
}
}
await encoder.FlushAsync();
encoder = null;
//Read bytes
byte[] outBytes = new byte[encoded.Size];
await encoded.AsStream().ReadAsync(outBytes, 0, outBytes.Length);
encoded.Dispose();
encoded = null;
//Create Base64
image = await ByteArrayToBitmapImage(outBytes);
System.Diagnostics.Debug.WriteLine("Pixel width: " + image.PixelWidth + " height: " + image.PixelHeight);
base64 = Convert.ToBase64String(outBytes);
Array.Clear(outBytes, 0, outBytes.Length);
await imageStream.FlushAsync();
imageStream.Dispose();
}
DateTime endtime = DateTime.Now;
TimeSpan span = (endtime - starttime);
//Kind of a hacky way to prevent high RAM usage and even crashing, remove when overal RAM usage has been lowered
GC.Collect();
System.Diagnostics.Debug.WriteLine("Making the picture took: " + span.Seconds + " seconds");
if (image != null)
{
RaisePropertyChanged("CapturedImage");
//Tell both UsePictureCommand and ResetCommand that the situation has changed.
((RelayedCommand)UsePictureCommand).RaiseCanExecuteChanged();
((RelayedCommand)ResetCommand).RaiseCanExecuteChanged();
}
else
{
throw new InvalidOperationException("Imagestream is not valid");
}
}
If there is any more information needed feel free to comment, I will try to put it out as fast as possible, thanks for reading.
The aspect ratio of the preview has to match the aspect ratio of the captured photo, or you'll get artifacts like that one in your capture (although it actually depends on the driver implementation, so it may vary from device to device).
Use the MediaCapture.VideoDeviceController.GetMediaStreamProperties() method on the MediaStream.VideoPreview.
Get the resulting VideoEncodingProperties, and use the Width and Height to figure out the aspect ratio.
Call MediaCapture.VideoDeviceController.GetAvailableMediaStreamProperties() on the MediaStream.Photo, and find out which ones have an aspect ratio that matches (I recommend using a tolerance value, something like 0.015f might be good)
Out of the ones that do, choose which one better suits your needs, e.g. by paying attention to the total resolution W * H
Apply your selection by calling MediaCapture.VideoDeviceController.SetMediaStreamPropertiesAsync() on the MediaStream.Photo and passing the encoding properties you want.
More information in this thread. An SDK sample is available here.

Easy way to clean metadata from an image?

I'm working on a service for a company project that handles image processing, and one of the methods is supposed to clean the metadata from an image passed to it.
I think implementation I currently have works, but I'm not sure if it's affecting the quality of images or if there's a better way to handle this task. Could you let me know if you know of a better way to do this?
Here's the method in question:
public byte[] CleanMetadata(byte[] data)
{
Image image;
if (tryGetImageFromBytes(data, out image))
{
Bitmap bitmap = new Bitmap(image);
using (var graphics = Graphics.FromImage(bitmap))
{
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.CompositingMode = CompositingMode.SourceCopy;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphics.DrawImage(image, new Point(0, 0));
}
ImageConverter converter = new ImageConverter();
return (byte[])converter.ConvertTo(image, typeof(byte[]));
}
return null;
}
And, for reference, the tryGetImageFromBytes method:
private bool tryGetImageFromBytes(byte[] data, out Image image)
{
try
{
using (var ms = new MemoryStream(data))
{
image = Image.FromStream(ms);
}
}
catch (ArgumentException)
{
image = null;
return false;
}
return true;
}
To reiterate: is there a better way to remove metadata from an image that doesn't involve redrawing it?
Thanks in advance.
The .NET way: You may want to try your hand at the System.Windows.Media.Imaging.BitmapEncoder class - more precisely, its Metadata collection. Quoting MSDN:
Metadata - Gets or sets the metadata that will be associated with this
bitmap during encoding.
The 'Oops, I (not so accidentally) forgot something way: Open the original bitmap file into a System.drawing.Bitmap object. Clone it to a new Bitmap object. Write the clone's contents to a new file. Like this one-liner:
((System.Drawing.Bitmap)System.Drawing.Image.FromFile(#"C:\file.png").Clone()).Save(#"C:\file-nometa.png");
The direct file manipulation way (only for JPEG): Blog post about removing the EXIF area.
I would suggest this, the source is here: Removing Exif-Data for jpg file
Changing a bit the 1st function
public Stream PatchAwayExif(Stream inStream)
{
Stream outStream = new MemoryStream();
byte[] jpegHeader = new byte[2];
jpegHeader[0] = (byte)inStream.ReadByte();
jpegHeader[1] = (byte)inStream.ReadByte();
if (jpegHeader[0] == 0xff && jpegHeader[1] == 0xd8) //check if it's a jpeg file
{
SkipAppHeaderSection(inStream);
}
outStream.WriteByte(0xff);
outStream.WriteByte(0xd8);
int readCount;
byte[] readBuffer = new byte[4096];
while ((readCount = inStream.Read(readBuffer, 0, readBuffer.Length)) > 0)
outStream.Write(readBuffer, 0, readCount);
return outStream;
}
And the second function with no changes, as post
private void SkipAppHeaderSection(Stream inStream)
{
byte[] header = new byte[2];
header[0] = (byte)inStream.ReadByte();
header[1] = (byte)inStream.ReadByte();
while (header[0] == 0xff && (header[1] >= 0xe0 && header[1] <= 0xef))
{
int exifLength = inStream.ReadByte();
exifLength = exifLength << 8;
exifLength |= inStream.ReadByte();
for (int i = 0; i < exifLength - 2; i++)
{
inStream.ReadByte();
}
header[0] = (byte)inStream.ReadByte();
header[1] = (byte)inStream.ReadByte();
}
inStream.Position -= 2; //skip back two bytes
}
Creating a new bitmap will clear out all the exif data.
var newImage = new Bitmap(image);
If you want to remove only specific info:
private Image RemoveGpsExifInfo(Image image)
{
foreach (var item in image.PropertyItems)
{
// GPS range is from 0x0000 to 0x001F. Full list here -> https://exiftool.org/TagNames/EXIF.html (click on GPS tags)
if (item.Id <= 0x001F)
{
image.RemovePropertyItem(item.Id);
}
}
return image;
}

GDI+ error upon upload multiple images then create thumbnails

I've got an image upload page that works just fine when I only upload the files.
I added a 'Create Thumbnail' function. It looks like the file system has a handle on the images when the thumbnail process starts.
I get the 'unspecified GDI+ error' only when the image is over about 250K. When the files are below 250K, thumbnails are created as expected.
What are my options? Is there an elegant solution here? I want something not hacky.
Also, I am using HttpFileCollection so we can upload multiple images at one time. I've tried to use .Dispose on the Thumbnail creation, but it fails before we get to this point.
public void Upload_Click(object Sender, EventArgs e)
{
string directory = Server.MapPath(#"~\images\");
HttpFileCollection hfc = Request.Files;
for (int i = 0; i < hfc.Count; i++)
{
HttpPostedFile hpf = hfc[i];
if (hpf.ContentLength > 0)
{
string fileName = hpf.FileName;
fileName = fileName.Replace(" ", "");
hpf.SaveAs(fileName);
createThumbnail(fileName);
}
}
}
private void createThumbnail(string filename)
{
Image image = Image.FromFile(filename);
Image thumb = image.GetThumbnailImage(100,100, () => false, IntPtr.Zero);
thumb.Save(filename);
image.Dispose();
thumb.Dispose();
}
Please let me know if this works any better:
public string ImageDirectory { get { return Server.MapPath(#"~\images\"); } }
public void OnUploadClick(object sender, EventArgs e)
{
var files = HttpContext.Request.Files.AllKeys.AsEnumerable()
.Select(k =>HttpContext.Request.Files[k]);
foreach(var file in files)
{
if(file.ContentLength <= 0)
continue;
string savePath = GetFullSavePath(file);
var dimensions = new Size(100, 100);
CreateThumbnail(file,savePath,dimensions);
}
}
private void CreateThumbnail(HttpPostedFile file,string savePath, Size dimensions)
{
using (var image = Image.FromStream(file.InputStream))
{
using (var thumb = image.GetThumbnailImage(dimensions.Width, dimensions.Height, () => false, IntPtr.Zero))
{
thumb.Save(savePath);
}
}
}
private string GetFullSavePath(HttpPostedFile file)
{
string fileName = System.IO.Path.GetFileName(file.FileName).Replace(" ", "");
string savePath = System.IO.Path.Combine(this.ImageDirectory, fileName);
return savePath;
}
Edit -
The foreach should have followed more to this pattern:
var files = HttpContext.Request.Files.AllKeys.AsEnumerable()
.Select(k =>HttpContext.Request.Files[k]);
foreach(var file in files)
{
}
You can try this code to create your thumbnails.
MemoryStream ms = new MemoryStream(File.ReadAllBytes(path));
Bitmap originalBMP = new Bitmap(ms);
int maxWidth = 200;
int maxHeight = 200;
// Calculate the new image dimensions
int origWidth = originalBMP.Width;
int origHeight = originalBMP.Height;
double sngRatio = Convert.ToDouble(origWidth) / Convert.ToDouble(origHeight);
// New dimensions
int newWidth = 0;
int newHeight = 0;
try
{
// max 200 by 200
if ((origWidth <= maxWidth && origHeight <= maxHeight) || origWidth <= maxWidth)
{
newWidth = origWidth;
newHeight = origHeight;
}
else
{
// Width longer (shrink width)
newWidth = 200;
newHeight = Convert.ToInt32(Convert.ToDouble(newWidth) / sngRatio);
}
// Create a new bitmap which will hold the previous resized bitmap
Bitmap newBMP = new Bitmap(originalBMP, newWidth, newHeight);
// Create a graphic based on the new bitmap
Graphics oGraphics = Graphics.FromImage(newBMP);
// Set the properties for the new graphic file
oGraphics.SmoothingMode = SmoothingMode.AntiAlias;
oGraphics.InterpolationMode = InterpolationMode.High;
// Draw the new graphic based on the resized bitmap
oGraphics.CompositingQuality = CompositingQuality.HighSpeed;
oGraphics.DrawImage(originalBMP, 0, 0, newWidth, newHeight);
// Save the new graphic file to the server
EncoderParameters p = new EncoderParameters(1);
p.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Compression, 70); // Percent Compression
MemoryStream savedBmp = new MemoryStream();
newBMP.Save(savedBmp, ImageCodecInfo.GetImageEncoders()[1], p);
// Once finished with the bitmap objects, we deallocate them.
originalBMP.Dispose();
newBMP.Dispose();
oGraphics.Dispose();
savedBmp.Dispose();
Certainly a bit more work but it does give you greater control.

Categories