C# CopyFromScreen multiple times to same file - c#

I am trying to take multiple screenshots and save them to a file. However, screenshots are taken decently often, and in order to not lose any of them, my current program will simply create a new file for every screenshot. Ideally, the program would simply "append" the most recent screenshot onto a single file every time.
Here is the code:
static Rectangle bounds = Screen.GetBounds(Point.Empty);
static Size rectSize = new Size(bounds.Width, bounds.Height);
public static void takeScreenshot(string path, int iteration, string filetype)
{
using (Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height))
{
using (Graphics g = Graphics.FromImage(bitmap))
{
g.CopyFromScreen(Point.Empty, Point.Empty, new Size
(rectSize.Width * iteration, rectSize.Height));
}
bitmap.Save(path + filetype);
}
}
iteration was the amount of times the method has been called. I was trying to just shift the next screenshot over by one screenshot's width while keeping every other screenshot, but it seems to overwrite the file anyways. Is it possible to do this?

Try this..
string n = string.Format("{0:yyyy-MM-dd_hh-mm-ss-tt}", DateTime.Now);
bitmap.Save(path + n + filetype);

Like TheGeneral said, you will run out of memory quickly. And every time the file grows it has a chance of needing to be moved on disc wearing down your drives.
Also, opening and viewing huge image files can be very slow, your computer will not be able to handle it very quickly.
But here it is, maybe at least the AppendImage function could be used for something like generating particle sprite strips in games or something.
If you're appending a screenshot, I wouldn't append more than maybe 10 times, with 1080P monitor.
public enum AppendLocation
{
Before,
After
}
void AppendScreenToFile(string filename, AppendLocation appendLocation = AppendLocation.After)
{
Rectangle bounds = Screen.PrimaryScreen.Bounds;
using (Bitmap screenShot = new Bitmap(bounds.Width, bounds.Height))
{
using (Graphics g = Graphics.FromImage(screenShot))
{
g.CopyFromScreen(UpperLeftSource, Point.Empty, bounds.Size);
}
if (!File.Exists(filename))
{
screenShot.Save(filename, ImageFormat.Png);
return;
}
//Not using Image.FromFile as it blocks saving.
Image onDisc;
using (Stream fs = new FileStream(filename, FileMode.Open, FileAccess.Read))
onDisc = Image.FromStream(fs);
using (Image appended = appendLocation == AppendLocation.Before ? AppendImage(screenShot, onDisc) : AppendImage(onDisc, screenShot))
appended.Save(filename, ImageFormat.Png);
}
}
public enum AppendAxis
{
Vertical,
Horizontal
}
Bitmap AppendImage(Image image, Image append, AppendAxis axis = AppendAxis.Vertical)
{
Bitmap bitmap;
Rectangle destinationRect;
RectangleF imageBounds = new Rectangle(0, 0, image.Width, image.Height);
RectangleF appendRect = new Rectangle(0, 0, append.Width, append.Height);
pictureBox_Item4.BackgroundImage = image;
pictureBox_Item3.BackgroundImage = append;
switch (axis)
{
case AppendAxis.Vertical:
destinationRect = new Rectangle(0, image.Height, append.Width, append.Height);
bitmap = new Bitmap(image.Width, image.Height + append.Height);
break;
case AppendAxis.Horizontal:
destinationRect = new Rectangle(image.Width, 0, append.Width, append.Width);
bitmap = new Bitmap(image.Width + append.Width, image.Height);
break;
default:
throw new ArgumentException("AppendAxis is invalid.");
}
using (Graphics g = Graphics.FromImage(bitmap))
{
g.DrawImage(image, imageBounds, imageBounds, GraphicsUnit.Pixel);
g.DrawImage(append, destinationRect, appendRect, GraphicsUnit.Pixel);
return bitmap;
}
}

Related

C# screen recorder reduce AVI video file size

I'm currently trying to make a screen recorder in C#, and so far it works but the problem is that something as simple as a 20second video will take about 1GB of space. I have it setup so a timer continuously takes screenshots with this method:
void takeScreenshot()
{
Rectangle bounds = Screen.FromControl(this).Bounds;
using (Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height))
{
using (Graphics g = Graphics.FromImage(bitmap))
{
//Add screen to bitmap:
g.CopyFromScreen(new Point(bounds.Left, bounds.Top), Point.Empty, bounds.Size);
}
//Create and save screenshot:
string name = path + "//screenshot-" + fileCount + ".jpeg";
bitmap.Save(name, ImageFormat.Jpeg);
inputImageSequence.Add(name);
fileCount++;
//Dispose of bitmap:
bitmap.Dispose();
}
}
And then it stores those pictures in a temporary folder in the D:// drive, and then when it's done it takes all the pictures and creates an AVI video out of them like this:
//Set bounds of video to screen size:
Rectangle bounds = Screen.FromControl(this).Bounds;
int width = bounds.Width;
int height = bounds.Height;
var framRate = 5;
using (var vFWriter = new VideoFileWriter())
{
//Create new video file:
vFWriter.Open(outputPath+"//video.avi", width, height, framRate, VideoCodec.Raw);
//Make each screenshot into a video frame:
foreach (var imageLocation in inputImageSequence)
{
Bitmap imageFrame = System.Drawing.Image.FromFile(imageLocation) as Bitmap;
vFWriter.WriteVideoFrame(imageFrame);
imageFrame.Dispose();
}
vFWriter.Close();
}
//Delete the screenshots and temporary folder:
DeletePath(path);
Any help on reducing the inefficiency of this is appreciated, I'm fairly new to this kind of programming.

Out of memory when cropping a bitmap with .Clone()

I am trying to automatically generate a thumbnail from an image uploaded by a user but I keep getting the exception "Out of memory".
From what I understand the out of memory exception is thrown when you specify a starting position or a width/height that's outside of the image but even if I do this
var rct = new Rectangle(5, 5, 10, 10);
var whatever = bitmap.Clone(rct, bitmap.PixelFormat);
on an image that is 800x900 pixels I still get the "Out of memory" exception, I can't figure out what's wrong with it and I can't really get any good answers from other threads since everything regarding the OOM exception is just the mistake of going outside the image boundaries.
Does anyone have an explanation or solution to this?
EDIT: A bit more context
The loop for the images.
foreach (var blob in fileInfoList)
{
var blockBlobName = CheckExistence(BaseBlobUrl, blob.FileName, blob.FileNameWithoutExtension);
var image = new Image()
{
BlobUrl = Path.Combine(BaseBlobUrl, blockBlobName),
FullName = blob.FileName,
FileName = blob.FileNameWithoutExtension,
BlockBlobName = blockBlobName,
OwningOrganizationId = CurrentUser.UserOrganization.OrganizationId,
ThumbnailUrl = CreateThumbnail(blob.File, blockBlobName),
Name = "Whatever"
};
blobList.Add(image);
RepositoryFactory.AzureStorageRepository.SaveImage(blob.File, blockBlobName, blob.ContentType, CurrentUser.UserOrganization.Organization.Id);
}
The method that is being called by each image in the list to generate the thumbnail.
public string CreateThumbnail(byte[] b, string parentImageName)
{
Bitmap bmp;
using (var ms = new MemoryStream(b))
{
bmp = new Bitmap(ms);
}
Bitmap thumbnail = bmp;
Rectangle rect = new Rectangle(5, 5, 10, 10);
if (bmp.Width > bmp.Height)
thumbnail = bmp.Clone(rect, bmp.PixelFormat);
else if (bmp.Height > bmp.Width)
thumbnail = bmp.Clone(new Rectangle((bmp.Height/2) - (bmp.Width/2), 0, bmp.Width, bmp.Width), bmp.PixelFormat);
byte[] bmpArray = new byte[0];
using (var ms = new MemoryStream())
{
finalCrop.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
ms.Close();
bmpArray = ms.ToArray();
}
var name = "Thumbnail_" + parentImageName;
RepositoryFactory.AzureStorageRepository.SaveThumbnail(bmpArray, name, "jpg/image", CurrentUser.UserOrganization.Organization.Id);
return BaseBlobUrl + "thumbnails/" + name;
}
I think the problem you're getting here is that Bitmaps need to be disposed. If one gets garbage collected without releasing its underlying unmanaged content (i.e. being disposed), then that memory can not be recovered...
Also note that you will need to dispose both bitmaps. Best thing to do is wrap them in a using, something like this:
using (var ms = new MemoryStream(b))
{
using (Bitmap bmp = new Bitmap(ms))
using (Bitmap thumbnail = bmp)
{
Rectangle rect = new Rectangle(5, 5, 10, 10);
if (bmp.Width > bmp.Height)
thumbnail = bmp.Clone(rect, bmp.PixelFormat);
else if (bmp.Height > bmp.Width)
thumbnail = bmp.Clone(new Rectangle((bmp.Height / 2) - (bmp.Width / 2), 0, bmp.Width, bmp.Width), bmp.PixelFormat);
byte[] bmpArray = new byte[0];
using (var ms = new MemoryStream())
{
finalCrop.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
ms.Close();
bmpArray = ms.ToArray();
}
var name = "Thumbnail_" + parentImageName;
RepositoryFactory.AzureStorageRepository.SaveThumbnail(bmpArray, name, "jpg/image", CurrentUser.UserOrganization.Organization.Id);
return BaseBlobUrl + "thumbnails/" + name;
}
}
It is worth noting that using will call Dispose() on it's target, even if an exception is thrown (thus having the same finally type functionality as #Scott Chamberlain answer
Okay so I found an answer to my problem by disposing the Bitmaps.
After this bit of code
Bitmap thumbnail = bmp;
I added
bmp.Dispose();
And during debugging I noticed that none of the properties from bmp were left in the Bitmap called thumbnail so I changed it into the following
Bitmap thumbnail = new Bitmap(bmp);
Thank you all for telling me to dispose the Bitmaps!
Here is the correct way to properly dispose of your objects.
Bitmap bmp = null;
Bitmap thumbnail = null;
try
{
using (var ms = new MemoryStream(b))
{
bmp = new Bitmap(ms);
}
Rectangle rect = new Rectangle(5, 5, 10, 10);
if (bmp.Width > bmp.Height)
thumbnail = bmp.Clone(rect, bmp.PixelFormat);
else if (bmp.Height > bmp.Width)
thumbnail = bmp.Clone(new Rectangle((bmp.Height/2) - (bmp.Width/2), 0, bmp.Width, bmp.Width), bmp.PixelFormat);
else
thumbnail = bmp;
byte[] bmpArray = new byte[0];
using (var ms = new MemoryStream())
{
finalCrop.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
ms.Close();
bmpArray = ms.ToArray();
}
var name = "Thumbnail_" + parentImageName;
RepositoryFactory.AzureStorageRepository.SaveThumbnail(bmpArray, name, "jpg/image", CurrentUser.UserOrganization.Organization.Id);
return BaseBlobUrl + "thumbnails/" + name;
}
finally
{
if(bmp != null)
bmp.Dispose();
if(thumbnail != null)
thumbnail.Dispose(); //If bmp and thumbnail are the same object this is still safe to do.
}
Use a try/finally block to ensure that even in the event of a error your objects get disposed. Doing the extra Bitmap thumbnail = new Bitmap(bmp); in your answer just makes a extra bitmap you are forgetting to dispose.
I received this Exception when the Rectangle used to crop the image was partly outside the bounds of the image.

GDI+ Fails to save PNG C#

Edit:
This appears to be a filesize issue. The filesize that I'm working with is 10600 x 700 pixels. This ends up being a ~280 MB bitmap. I've attempted saving significantly smaller file sizes (10x10) as PNG, where they correctly save out. Is there a size limitation that is being hit with GDI+?
Pretty much full code:
class Converter
{
/* Each PNG below is 50x50 */
private static readonly int BLOCK_SIZE = 50;
private static Dictionary<char, Bitmap> BlockLookup => new Dictionary<char, Bitmap>
{
{'R', new Bitmap("Content/Map/Blocks/RedBlock.png")},
{'G', new Bitmap("Content/Map/Blocks/GreenBlock.png")},
{'B', new Bitmap("Content/Map/Blocks/BlueBlock.png")},
{'P', new Bitmap("Content/Map/Blocks/PurpleBlock.png")},
{'Y', new Bitmap("Content/Map/Blocks/YellowBlock.png")},
{'O', new Bitmap("Content/Map/Blocks/OrangeBlock.png")}
};
public void Convert(string textFilePath, string outputDirectory)
{
List<string> fileContents = new List<string>();
/* Irrelevant loading code snipped, loads text file into fileContents */
int width = fileContents.Max(line => line.Length) * BLOCK_SIZE; // 10600
int height = fileContents.Count * BLOCK_SIZE; // 700
/* Try to convert our text-file-as-image into a real image, mapping chars to their blocks */
using (Bitmap bitmap = new Bitmap(width, height))
{
using (Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.Clear(Color.WhiteSmoke);
graphics.SmoothingMode = SmoothingMode.AntiAlias;
for (int y = 0; y < fileContents.Count; ++y)
{
for (int x = 0; x < fileContents[y].Length; ++x)
{
char currentCharacter = fileContents[y][x];
if (!blockLookup.ContainsKey(currentCharacter))
{
continue;
}
Bitmap mapBlock = blockLookup[currentCharacter];
var mapX = x * BLOCK_SIZE;
var mapY = y * BLOCK_SIZE;
graphics.DrawImage(mapBlock, new Point(mapX, mapY));
}
}
graphics.Flush(FlushIntention.Sync);
graphics.Save();
}
if (!Directory.Exists(outputDirectory))
{
Directory.CreateDirectory(outputDirectory);
}
bitmap.Save(outputDirectory + mapName, ImageFormat.Png);
}
}
}
I have the following code:
using (Bitmap bitmap = new Bitmap(width, height))
{
using (Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.Clear(Color.WhiteSmoke);
/* SNIP, a bunch of image manipulation here */
graphics.Flush();
}
if (!Directory.Exists(outputDirectory))
{
Directory.CreateDirectory(outputDirectory);
}
bitmap.Save(outputDirectory + imageName, ImageFormat.Bmp);
}
This code works great. However, if I change the ImageFormat from ImageFormat.Bmp to be ImageFormat.Png, I get a "A generic error occurred in GDI+.". I've scoured stackoverflow and google. The files do not exist, the path does. I've deleted all output files. I've tried 64bit & 32bit builds. Even stuff like copying the Bitmap and writing to memory streams fails (the below does not work):
using (Bitmap bitmap = new Bitmap(width, height))
{
using (Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.Clear(Color.WhiteSmoke);
/* SNIP, a bunch of image manipulation here */
graphics.Flush();
}
if (!Directory.Exists(outputDirectory))
{
Directory.CreateDirectory(outputDirectory);
}
using (MemoryStream memoryStream = new MemoryStream())
{
Bitmap temp = new Bitmap(bitmap);
temp.Save(memoryStream, ImageFormat.Png);
}
}
I'd really like to be able to output PNG files. However, there seems to be some issue that I'm unaware of with (my version?) of GDI and PNG files. Is there anything I can do to get PNG output?
Sorry, but your Code is working great - I tested it with VS 2013 Express for Win Desktop on Windows Server 2012 R2 - not a problem at all.
Test 1:
private int width = 10600;
private int height = 700;
using (Bitmap bitmap = new Bitmap(width, height))
{
using (Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.Clear(Color.Black);
/* SNIP, a bunch of image manipulation here */
graphics.Flush();
}
bitmap.Save("image.png", ImageFormat.Png);
}
}
Test 2:
private int width = 10600;
private int height = 700;
using (Bitmap bitmap = new Bitmap(width, height))
{
using (Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.Clear(Color.Black);
/* SNIP, a bunch of image manipulation here */
//I read a little around - maybe your graphics operations are out of sync?
graphics.Flush(FlushIntention.Sync);
graphics.Save();
}
using (MemoryStream memoryStream = new MemoryStream())
{
Bitmap temp = new Bitmap(bitmap);
//temp.Save("image.png", ImageFormat.Png); //this worked obviously
temp.Save(memoryStream, ImageFormat.Png);
pictureBox1.Image = Image.FromStream(memoryStream); //a huge black box appears - everything is working fine
}
}
}
Maybe you have a system-issue?
I meant to comment this, but not enough reputation =,(
Maybe this isn't worth as an answer and should be a comment. But I can move it if you want to :)
Indeeed Windows 7 has some problems with big pictures, not sure its GDI+ related or just deeper in the core. But I programmed a photoediting software for university and noticed, that I couldn't load and/or format pictures larger than ~180MB. I found a workaround by piping it in an array and slicing it. But I don't know if you can use that. If I find that code I will update this post.

Graphics.DrawImage() - Throws out of memory exception

I have some images that I need to do some crude re-size work on -- For the purpose of this example lets just say I need to increase the width and height of a given image by 4 pixels.
I am unsure why the call to Graphics.DrawImage() is throwing an OOM -- Any advice here would be greatly appreciated.
class Program
{
static void Main(string[] args)
{
string filename = #"c:\testImage.png";
// Load png from stream
FileStream fs = new FileStream(filename, FileMode.Open);
Image pngImage = Image.FromStream(fs);
fs.Close();
// super-hacky resize
Graphics g = Graphics.FromImage(pngImage);
g.DrawImage(pngImage, 0, 0, pngImage.Width + 4, pngImage.Height + 4); // <--- out of memory exception?!
// save it out
pngImage.Save(filename, System.Drawing.Imaging.ImageFormat.Png);
}
}
I just had the same problem. However fixing the size of the output Graphics did not solve my problem. I realized that I tried to use a very high quality for the drawing the image which was consuming too much memory when I use the code on a lot of images.
g.CompositingQuality = CompositingQuality.HighQuality;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.SmoothingMode = SmoothingMode.HighQuality;
after commenting these lines out the code worked perfectly.
Your Graphics surface is only big enough for the original-sized image. You need to create a new image of the correct size and use it as the source of your Graphics object.
Image newImage = new Bitmap(pngImage.Width + 4, pngImage.Height+4);
Graphics g = Graphics.FromImage(newImage);
That likely will not accomplish what you'd like to do seeing as how the image is the same size as the one specified by FromImage, instead you can use the Bitmap class:
using (var bmp = new Bitmap(fileName))
{
using (var output = new Bitmap(
bmp.Width + 4, bmp.Height + 4, bmp.PixelFormat))
using (var g = Graphics.FromImage(output))
{
g.DrawImage(bmp, 0, 0, output.Width, output.Height);
output.Save(outFileName, ImageFormat.Png);
}
}
Can you try this fix?
class Program
{
static void Main(string[] args)
{
string filename = #"c:\testImage.png";
// Load png from stream
FileStream fs = new FileStream(filename, FileMode.Open);
Image pngImage = Image.FromStream(fs);
fs.Close();
// super-hacky resize
Graphics g = Graphics.FromImage(pngImage);
pngImage = pngImage.GetThumbnailImage(image.Width, image.Height, null, IntPtr.Zero);
g.DrawImage(pngImage, 0, 0, pngImage.Width + 4, pngImage.Height + 4); // <--- out of memory exception?!
// save it out
pngImage.Save(filename, System.Drawing.Imaging.ImageFormat.Png);
}
}
Inspired by this question: Help to resolve 'Out of memory' exception when calling DrawImage

Image resizing with GDI+

I'm really trying to nail out a little more performance out of this tidbit of code. It's not a heavly used bit of code but is used every time a new image is uploaded, and 4 times for each image (100px, 200px, 500px, 700px). So when there are any more than 2 or 3 images processing, it gets a little busy on the server. Also I'm trying to figure out how to make it correctly process images with a low resolution. Currently it just chops it off half way through, not plesent.
Examples: Original, large, xLarge
public static byte[] ResizeImageFile(byte[] imageFile, int targetSize)
{
using (System.Drawing.Image oldImage = System.Drawing.Image.FromStream(new MemoryStream(imageFile)))
{
Size newSize = CalculateDimensions(oldImage.Size, targetSize);
using (Bitmap newImage = new Bitmap(newSize.Width, newSize.Height, PixelFormat.Format32bppRgb))
{
newImage.SetResolution(oldImage.HorizontalResolution, oldImage.VerticalResolution);
using (Graphics canvas = Graphics.FromImage(newImage))
{
canvas.SmoothingMode = SmoothingMode.AntiAlias;
canvas.InterpolationMode = InterpolationMode.HighQualityBicubic;
canvas.PixelOffsetMode = PixelOffsetMode.HighQuality;
canvas.DrawImage(oldImage, new Rectangle(new Point(0, 0), newSize));
MemoryStream m = new MemoryStream();
newImage.Save(m, ImageFormat.Jpeg);
return m.GetBuffer();
}
}
}
}
private static Size CalculateDimensions(Size oldSize, int targetSize)
{
Size newSize = new Size();
if (oldSize.Width > oldSize.Height)
{
newSize.Width = targetSize;
newSize.Height = (int)(oldSize.Height * (float)targetSize / (float)oldSize.Width);
}
else
{
newSize.Width = (int)(oldSize.Width * (float)targetSize / (float)oldSize.Height);
newSize.Height = targetSize;
}
return newSize;
}
Thanks for and help!
The first thought that comes to mind is, have you thought about Multithreading it? i.e. calling this method for each image (or batch of images) in a separate thread? That way, if your server has a few cores you can get things done quicker. Just a thought...
(Threading is a great tip.)
Try to call your method with the smallest possible image as input each time, instead of the original image. If the original image is, say 2000px, then create the 700px image from it and then use your newly created 700px image to create the 500px, etc...
With the HighQualityBicubic setting I doubt that you'll notice any difference in the 100px image. (But it of course it needs to be verified.)
For completeness, here is the solution to the second part of the question which was never answered. When processing a low resolution image the image was being cut off. The solution now, seems obvious. The problem lies in this bit of code from above:
using (Bitmap newImage = new Bitmap(newSize.Width, newSize.Height,
PixelFormat.Format32bppRgb))
The problem being that I'm selecting the PixelFormat, not letting it be the format of the original image. The correct code is here:
public static byte[] ResizeImageFile(byte[] imageFile, int targetSize)
{
using (System.Drawing.Image oldImage = System.Drawing.Image.FromStream(new MemoryStream(imageFile)))
{
Size newSize = CalculateDimensions(oldImage.Size, targetSize);
using (Bitmap newImage = new Bitmap(newSize.Width, newSize.Height,
oldImage.PixelFormat))
{
newImage.SetResolution(oldImage.HorizontalResolution,
oldImage.VerticalResolution);
using (Graphics canvas = Graphics.FromImage(newImage))
{
canvas.SmoothingMode = SmoothingMode.AntiAlias;
canvas.InterpolationMode = InterpolationMode.HighQualityBicubic;
canvas.PixelOffsetMode = PixelOffsetMode.HighQuality;
canvas.DrawImage(oldImage, new Rectangle(new Point(0, 0), newSize));
MemoryStream m = new MemoryStream();
newImage.Save(m, ImageFormat.Jpeg);
return m.GetBuffer();
}
}
}
}

Categories