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;
}
Related
I am trying to create code to export out the images within a PDF using iText Version 7.19. I'm having some issues with Flate encoded images. All the Flate encoded images from the Microsoft free book I'm using as an example (see Moving to Microsoft Visual Studio 2010) always coming out pink and depending upon how I try to copy the bytes they can come out distorted.
If I attempt to copy all the image bytes at once (see the SaveFlateEncodedImage2 method in the code below), they come out distorted like this one:
If I attempt to copy them row by row (see the SaveFlateEncodedImage method in the code below), they are pink like this one
Here is the code that I'm using to export them:
using iText.Kernel;
using iText.Kernel.Pdf;
using iText.Kernel.Pdf.Filters;
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
namespace ITextPdfStuff
{
public class MyPdfImageExtractor
{
private readonly string _pdfFileName;
public MyPdfImageExtractor(string pdfFileName)
{
_pdfFileName = pdfFileName;
}
public void ExtractToDirectory(string directoryName)
{
using (var reader = new PdfReader(_pdfFileName))
{
// Avoid iText.Kernel.Crypto.BadPasswordException: https://stackoverflow.com/a/48065052/97803
reader.SetUnethicalReading(true);
using (var pdfDoc = new PdfDocument(reader))
{
ExtractImagesOnAllPages(pdfDoc, directoryName);
}
}
}
private void ExtractImagesOnAllPages(PdfDocument pdfDoc, string directoryName)
{
Console.WriteLine($"Number of pdf {pdfDoc.GetNumberOfPdfObjects()} objects");
// Extract objects https://itextpdf.com/en/resources/examples/itext-7/extracting-objects-pdf
for (int objNumber = 1; objNumber <= pdfDoc.GetNumberOfPdfObjects(); objNumber++)
{
PdfObject currentObject = pdfDoc.GetPdfObject(objNumber);
if (currentObject != null && currentObject.IsStream())
{
try
{
ExtractImagesOneImage(currentObject as PdfStream, Path.Combine(directoryName, $"image{objNumber}.png"));
}
catch (Exception ex)
{
Console.WriteLine($"Object number {objNumber} is NOT an image! -- error: {ex.Message}");
}
}
}
}
private void ExtractImagesOneImage(PdfStream someStream, string fileName)
{
var pdfDict = (PdfDictionary)someStream;
string subType = pdfDict.Get(PdfName.Subtype)?.ToString() ?? string.Empty;
bool isImage = subType == "/Image";
if (isImage == false)
return;
bool decoded = false;
string filter = pdfDict.Get(PdfName.Filter).ToString();
if (filter == "/FlateDecode")
{
SaveFlateEncodedImage(fileName, pdfDict, someStream.GetBytes(false));
}
else
{
byte[] imgData;
try
{
imgData = someStream.GetBytes(decoded);
}
catch (PdfException ex)
{
imgData = someStream.GetBytes(!decoded);
}
SaveNormalImage(fileName, imgData);
}
}
private void SaveNormalImage(string fileName, byte[] imgData)
{
using (var memStream = new System.IO.MemoryStream(imgData))
using (var image = System.Drawing.Image.FromStream(memStream))
{
image.Save(fileName, ImageFormat.Png);
Console.WriteLine($"{Path.GetFileName(fileName)}");
}
}
private void SaveFlateEncodedImage(string fileName, PdfDictionary pdfDict, byte[] imgData)
{
int width = int.Parse(pdfDict.Get(PdfName.Width).ToString());
int height = int.Parse(pdfDict.Get(PdfName.Height).ToString());
int bpp = int.Parse(pdfDict.Get(PdfName.BitsPerComponent).ToString());
// Example that helped: https://stackoverflow.com/a/8517377/97803
PixelFormat pixelFormat;
switch (bpp)
{
case 1:
pixelFormat = PixelFormat.Format1bppIndexed;
break;
case 8:
pixelFormat = PixelFormat.Format8bppIndexed;
break;
case 24:
pixelFormat = PixelFormat.Format24bppRgb;
break;
default:
throw new Exception("Unknown pixel format " + bpp);
}
// .NET docs https://api.itextpdf.com/iText7/dotnet/7.1.9/classi_text_1_1_kernel_1_1_pdf_1_1_filters_1_1_flate_decode_strict_filter.html
// Java docs have more detail: https://api.itextpdf.com/iText7/java/7.1.7/com/itextpdf/kernel/pdf/filters/FlateDecodeFilter.html
imgData = FlateDecodeStrictFilter.FlateDecode(imgData, true);
// byte[] streamBytes = FlateDecodeStrictFilter.DecodePredictor(imgData, pdfDict);
// Copy the image one row at a time
using (var bmp = new Bitmap(width, height, pixelFormat))
{
BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, pixelFormat);
int length = (int)Math.Ceiling(width * bpp / 8.0);
for (int i = 0; i < height; i++)
{
int offset = i * length;
int scanOffset = i * bmpData.Stride;
Marshal.Copy(imgData, offset, new IntPtr(bmpData.Scan0.ToInt64() + scanOffset), length);
}
bmp.UnlockBits(bmpData);
bmp.Save(fileName, ImageFormat.Png);
}
Console.WriteLine($"FlateDecode! {Path.GetFileName(fileName)}");
}
/// <summary>This method distorts the image badly</summary>
private void SaveFlateEncodedImage2(string fileName, PdfDictionary pdfDict, byte[] imgData)
{
int width = int.Parse(pdfDict.Get(PdfName.Width).ToString());
int height = int.Parse(pdfDict.Get(PdfName.Height).ToString());
int bpp = int.Parse(pdfDict.Get(PdfName.BitsPerComponent).ToString());
// Example that helped: https://stackoverflow.com/a/8517377/97803
PixelFormat pixelFormat;
switch (bpp)
{
case 1:
pixelFormat = PixelFormat.Format1bppIndexed;
break;
case 8:
pixelFormat = PixelFormat.Format8bppIndexed;
break;
case 24:
pixelFormat = PixelFormat.Format24bppRgb;
break;
default:
throw new Exception("Unknown pixel format " + bpp);
}
// .NET docs https://api.itextpdf.com/iText7/dotnet/7.1.9/classi_text_1_1_kernel_1_1_pdf_1_1_filters_1_1_flate_decode_strict_filter.html
// Java docs have more detail: https://api.itextpdf.com/iText7/java/7.1.7/com/itextpdf/kernel/pdf/filters/FlateDecodeFilter.html
imgData = FlateDecodeStrictFilter.FlateDecode(imgData, true);
// byte[] streamBytes = FlateDecodeStrictFilter.DecodePredictor(imgData, pdfDict);
// Copy the entire image in one go
using (var bmp = new Bitmap(width, height, pixelFormat))
{
BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, pixelFormat);
Marshal.Copy(imgData, 0, bmpData.Scan0, imgData.Length);
bmp.UnlockBits(bmpData);
bmp.Save(fileName, ImageFormat.Png);
}
Console.WriteLine($"FlateDecode! {Path.GetFileName(fileName)}");
}
}
}
The code can be instantiated and called like this from within a .NET Core console application:
string existingFileName = #"c:\temp\ReallyLongBook1.pdf";
var imageExtractor = new MyPdfImageExtractor(existingFileName);
imageExtractor.ExtractToDirectory(#"c:\temp\images");
I'm running the following free Microsoft book through this code:
Moving to Microsoft Visual Studio 2010
The image in question is on page 10 and it's black and white (not pink).
I'm no PDF expert and I've been banging on this code for a couple of days now picking apart a number of examples to try to piece this together. Any help that would get me past my pink images, would be greatly appreciated.
-------Update Feb 4, 2020------
Here is the revised version after MKL's suggested changes. His change extracted more images than mine and produced proper looking images that appear in the book I mentioned above:
using iText.Kernel.Pdf;
using iText.Kernel.Pdf.Canvas.Parser;
using iText.Kernel.Pdf.Canvas.Parser.Data;
using iText.Kernel.Pdf.Canvas.Parser.Listener;
using iText.Kernel.Pdf.Xobject;
using System;
using System.Collections.Generic;
using System.IO;
namespace ITextPdfStuff
{
public class MyPdfImageExtractor
{
private readonly string _pdfFileName;
public MyPdfImageExtractor(string pdfFileName)
{
_pdfFileName = pdfFileName;
}
public void ExtractToDirectory(string directoryName)
{
using (var reader = new PdfReader(_pdfFileName))
{
// Avoid iText.Kernel.Crypto.BadPasswordException: https://stackoverflow.com/a/48065052/97803
reader.SetUnethicalReading(true);
using (var pdfDoc = new PdfDocument(reader))
{
ExtractImagesOnAllPages(pdfDoc, directoryName);
}
}
}
private void ExtractImagesOnAllPages(PdfDocument pdfDoc, string directoryName)
{
Console.WriteLine($"Number of pdf {pdfDoc.GetNumberOfPdfObjects()} objects");
IEventListener strategy = new ImageRenderListener(Path.Combine(directoryName, #"image{0}.{1}"));
PdfCanvasProcessor parser = new PdfCanvasProcessor(strategy);
for (var i = 1; i <= pdfDoc.GetNumberOfPages(); i++)
{
parser.ProcessPageContent(pdfDoc.GetPage(i));
}
}
}
public class ImageRenderListener : IEventListener
{
public ImageRenderListener(string format)
{
this.format = format;
}
public void EventOccurred(IEventData data, EventType type)
{
if (data is ImageRenderInfo imageData)
{
try
{
PdfImageXObject imageObject = imageData.GetImage();
if (imageObject == null)
{
Console.WriteLine("Image could not be read.");
}
else
{
File.WriteAllBytes(string.Format(format, index++, imageObject.IdentifyImageFileExtension()), imageObject.GetImageBytes());
}
}
catch (Exception ex)
{
Console.WriteLine("Image could not be read: {0}.", ex.Message);
}
}
}
public ICollection<EventType> GetSupportedEvents()
{
return null;
}
string format;
int index = 0;
}
}
PDFs internally support a very flexible bitmap image format, in particular as far as different color spaces are concerned.
iText in its parsing API supports export of a subset thereof, essentially the subset of images that easily can be exported as regular JPEGs or PNGs.
Thus, it makes sense to try and export using the iText parsing API first. You can do that as follows:
Directory.CreateDirectory(#"extract\");
using (PdfReader reader = new PdfReader(#"Moving to Microsoft Visual Studio 2010 ebook.pdf"))
using (PdfDocument pdfDocument = new PdfDocument(reader))
{
IEventListener strategy = new ImageRenderListener(#"extract\Moving to Microsoft Visual Studio 2010 ebook-i7-{0}.{1}");
PdfCanvasProcessor parser = new PdfCanvasProcessor(strategy);
for (var i = 1; i <= pdfDocument.GetNumberOfPages(); i++)
{
parser.ProcessPageContent(pdfDocument.GetPage(i));
}
}
with the helper class ImageRenderListener:
public class ImageRenderListener : IEventListener
{
public ImageRenderListener(string format)
{
this.format = format;
}
public void EventOccurred(IEventData data, EventType type)
{
if (data is ImageRenderInfo imageData)
{
try
{
PdfImageXObject imageObject = imageData.GetImage();
if (imageObject == null)
{
Console.WriteLine("Image could not be read.");
}
else
{
File.WriteAllBytes(string.Format(format, index++, imageObject.IdentifyImageFileExtension()), imageObject.GetImageBytes());
}
}
catch (Exception ex)
{
Console.WriteLine("Image could not be read: {0}.", ex.Message);
}
}
}
public ICollection<EventType> GetSupportedEvents()
{
return null;
}
string format;
int index = 0;
}
In case of your example document it exports nearly 400 images successfully, among them your example image above:
But there also are less than 30 images it cannot export, on standard out you'll find "Image could not be read: The color space /DeviceN is not supported.."
I have some task - resize image if height or width >500px.
I try this code.
But when I choosed image I have error like
NewImage.Save(path);
В GDI+ error of the general form.
private void button1_Click(object sender, EventArgs e)
{
OpenFileDialog fdlg = new OpenFileDialog();
fdlg.Multiselect = true;
if (fdlg.ShowDialog() == DialogResult.OK)
{
for (int i = 0; i < fdlg.FileNames.Length; i++)
{
string file = fdlg.FileNames[i];
string path = System.IO.Path.GetFullPath(file);
System.Drawing.Image img = System.Drawing.Image.FromFile(path);
if (img.Width > 500 || img.Height > 500)
{
int currW = img.Width;
int currH = img.Height;
int realWPer = 500 * 100 / currW;
int realHPer = 500 * 100 / currH;
int realW = currW / 100 * realWPer; // new width
int realH = currH / 100 * realHPer; // new height
Image NewImage = resizeImage(img, new Size(realW, realH));
NewImage.Save(path);
}
}
}
}
public static Image resizeImage(Image imgToResize, Size size)
{
return (Image)(new Bitmap(imgToResize, size));
}
You haven't posted the error message so one can only guess what may be wrong, I'd guess you get an error that you are trying to access a locked file.
You are trying to save the new image over the old image which is still open. You never close/dispose the img so it's still open when you try to save the new image using the same path as the old
Below is the offending code. The Stream is coming from a HttpPostedFile. Tested in local is fine.
Tested online also fine for the first time when overwriting. The exception is only thrown when you try to overwrite the file multiple times. Eg. Upload then Re-upload Any insights?
public void ResizeFromStream(string ImageSavePath, int MaxSideSize, Stream Buffer)
{
int intNewWidth;
int intNewHeight;
System.Drawing.Image imgInput = System.Drawing.Image.FromStream(Buffer);
//Determine image format
ImageFormat fmtImageFormat = imgInput.RawFormat;
//get image original width and height
int intOldWidth = imgInput.Width;
int intOldHeight = imgInput.Height;
//determine if landscape or portrait
int intMaxSide;
if (intOldWidth >= intOldHeight)
{
intMaxSide = intOldWidth;
}
else
{
intMaxSide = intOldHeight;
}
Bitmap bmpResized = null;
if (intMaxSide > MaxSideSize)
{
//set new width and height
double dblCoef = MaxSideSize / (double)intMaxSide;
intNewWidth = Convert.ToInt32(dblCoef * intOldWidth);
intNewHeight = Convert.ToInt32(dblCoef * intOldHeight);
bmpResized = new Bitmap(imgInput, intNewWidth, intNewHeight);
}
else
{
intNewWidth = intOldWidth;
intNewHeight = intOldHeight;
bmpResized = new Bitmap(Buffer);
}
//save bitmap to disk
bmpResized.Save(ImageSavePath, fmtImageFormat);
//release used resources
imgInput.Dispose();
bmpResized.Dispose();
Buffer.Dispose();
}
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;
}
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.