I'm trying to crop gif images in c# without using an add-ons with drawing a rectangle on the image (and I've got it to work with normal image), but .. lets just say the output is less than optimal..
examples:
Origianl Image:
Cropped Image:
and here's the code that I'm using :
public override Image Crop(Rectangle f, bool isStrch, int slashwid, int slashhei)
{
_revert = Image;
GifDecoder();
widmult = Width / (double)slashwid;
heimult = Height / (double)slashhei;
double num = (double)f.X * widmult;
double num2 = (double)f.Y * heimult;
double num3 = (double)f.Width * widmult;
double num4 = (double)f.Height * heimult;
Rectangle srcRect = new Rectangle(isStrch ? ((int)num) : f.X, isStrch ? ((int)num2) : f.Y, isStrch ? ((int)num3) : f.Width, isStrch ? ((int)num4) : f.Height);
CroppedFrames = new Image[Frames.Length];
for (int i = 0; i < Frames.Length; i++)
{
Bitmap image = new Bitmap(Frames[i]);
Bitmap bitmap = new Bitmap(srcRect.Width, srcRect.Height);
using (Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.DrawImage(image, new Rectangle(0, 0, bitmap.Width, bitmap.Height), srcRect, GraphicsUnit.Pixel);
}
CroppedFrames[i] = bitmap;
}
GifEncoder(f.Width,f.Height);
return Image;
}
private void GifDecoder()
{
Stream bitmapStream = new FileStream(Path, FileMode.Open, FileAccess.Read, FileShare.Read);
GifBitmapDecoder gifBitmapDecoder = new GifBitmapDecoder(bitmapStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
Frames = new Image[gifBitmapDecoder.Frames.Count];
for (int i = 0; i < gifBitmapDecoder.Frames.Count; i++)
{
Frames[i] = BitmapFromSource(gifBitmapDecoder.Frames[i]);
}
}
private void GifEncoder(int width,int height)
{
BitmapPalette palette = new BitmapPalette(BitmapImageFromImage(Image),256);
int bytecount = (width * height) / 8;
using (FileStream fs = new FileStream(Path + "output.gif", FileMode.Create))
{
GifBitmapEncoder encoder = new GifBitmapEncoder();
for (int f = 0; f < Frames.Length; f++)
{
byte[] pixels = imageToByteArray(CroppedFrames[f]);
BitmapSource image = BitmapSource.Create(width,height,96,96,System.Windows.Media.PixelFormats.Indexed8,palette,pixels,width);
encoder.Frames.Add(BitmapFrame.Create(image));
}
encoder.Save(fs);
Image = Image.FromStream(fs);
fs.Close();
}
}
private byte[] imageToByteArray(Image imageIn)
{
MemoryStream ms = new MemoryStream();
Bitmap aaa = new Bitmap(imageIn);
aaa.Save(ms, ImageFormat.Png);
return ms.ToArray();
}
(bool isStrch, int slashwid, int slashhei) are for checking if the picturebox is smaller than the image or not to get accurate crops with the rect.
Related
I have a simple Byte array representing an image in gray scale (value come from 0 to 255)
There is an example :
Byte[] sample = new Byte[16] {
55 ,0 ,0 ,255,
12 ,255,75 ,255,
255,150,19 ,255,
42 ,255,78 ,255 };
I want to save this in jpeg format but I don't know how I can do this.
I've try 2 ways but it doesn't work. I think my byte array is not right formatted but I don't know how to do.
I have tried this :
TypeConverter tc = TypeDescriptor.GetConverter(typeof(Bitmap));
Bitmap bmp = (Bitmap)tc.ConvertFrom(sample);
bmp.Save(myPath, System.Drawing.Imaging.ImageFormat.Jpeg);
And this :
MemoryStream str = new MemoryStream(sample);
str.Position = 0;
using (Image image = Image.FromStream(str))
{
image.Save(myPath, System.Drawing.Imaging.ImageFormat.Jpeg);
}
With all your indications, I finally find a way :
public static void SaveByteArryToJpeg(string imagePath, Byte[] data, int width, int height)
{
if (width * height != data.Length)
throw new FormatException("Size does not match");
Bitmap bmp = new Bitmap(width, height);
for (int r = 0; r < height; r++)
{
for (int c = 0; c < width; c++)
{
Byte value = data[r * width + c];
bmp.SetPixel(c, r, Color.FromArgb(value, value, value));
}
}
bmp.Save(imagePath, System.Drawing.Imaging.ImageFormat.Jpeg);
}
BitmapImage photo = byteToImage(byte[] sample)
this functionto convert byte to BitmapImage
public BitmapImage byteToImage(byte[] buffer)
{
using(var ms = new MemoryStream(buffer))
{
var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = ms;
image.EndInit();
}
return image;
}
and then byteToImage will return an image, so you can save.
Hopefully this can help you
Am working in windows form.I have an image. Its size is 960*1280.
When i tried to add this image to my picture box at run time. the image is getting rotated and its size of the image is 1280*960.
my aim is to resize the image to 100*100 and then add to picture box. i don't want that image to get rotated. Can you suggest me some ideas?
put this in a class file and use the below resizer code
public class ImageResizer
{
private int allowedFileSizeInByte;
private string sourcePath;
private string destinationPath;
public ImageResizer()
{
}
public ImageResizer(int allowedSize, string sourcePath, string destinationPath)
{
allowedFileSizeInByte = allowedSize;
this.sourcePath = sourcePath;
this.destinationPath = destinationPath;
}
public void ScaleImage()
{
using (MemoryStream ms = new MemoryStream())
{
using (FileStream fs = new FileStream(sourcePath, FileMode.Open))
{
Bitmap bmp = (Bitmap)Image.FromStream(fs);
SaveTemporary(bmp, ms, 100);
while (ms.Length < 0.9 * allowedFileSizeInByte || ms.Length > allowedFileSizeInByte)
{
double scale = Math.Sqrt((double)allowedFileSizeInByte / (double)ms.Length);
ms.SetLength(0);
bmp = ScaleImage(bmp, scale);
SaveTemporary(bmp, ms, 100);
}
if (bmp != null)
bmp.Dispose();
SaveImageToFile(ms);
}
}
}
public byte[] GetScaledImage(int allowedSize, string sourcePath)
{
allowedFileSizeInByte = allowedSize;
this.sourcePath = sourcePath;
//this.destinationPath = destinationPath;
using (MemoryStream ms = new MemoryStream())
{
using (FileStream fs = new FileStream(sourcePath, FileMode.Open))
{
Bitmap bmp = (Bitmap)Image.FromStream(fs);
SaveTemporary(bmp, ms, 100);
while (ms.Length < 0.9 * allowedFileSizeInByte || ms.Length > allowedFileSizeInByte)
{
double scale = Math.Sqrt((double)allowedFileSizeInByte / (double)ms.Length);
ms.SetLength(0);
bmp = ScaleImage(bmp, scale);
SaveTemporary(bmp, ms, 100);
}
if (bmp != null)
bmp.Dispose();
Byte[] buffer = null;
if (ms != null && ms.Length > 0)
{
ms.Position = 0;
buffer = new byte[ms.Length];
ms.Read(buffer, 0, buffer.Length);
}
return buffer;
}
}
}
private void SaveImageToFile(MemoryStream ms)
{
byte[] data = ms.ToArray();
using (FileStream fs = new FileStream(destinationPath, FileMode.Create))
{
fs.Write(data, 0, data.Length);
}
}
private void SaveTemporary(Bitmap bmp, MemoryStream ms, int quality)
{
EncoderParameter qualityParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality);
var codec = GetImageCodecInfo();
var encoderParams = new EncoderParameters(1);
encoderParams.Param[0] = qualityParam;
if (codec != null)
bmp.Save(ms, codec, encoderParams);
else
bmp.Save(ms, GetImageFormat());
}
public Bitmap ScaleImage(Bitmap image, double scale)
{
int newWidth = (int)(image.Width * scale);
int newHeight = (int)(image.Height * scale);
Bitmap result = new Bitmap(newWidth, newHeight, PixelFormat.Format24bppRgb);
result.SetResolution(image.HorizontalResolution, image.VerticalResolution);
using (Graphics g = Graphics.FromImage(result))
{
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.CompositingQuality = CompositingQuality.HighQuality;
g.SmoothingMode = SmoothingMode.HighQuality;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
g.DrawImage(image, 0, 0, result.Width, result.Height);
}
return result;
}
private ImageCodecInfo GetImageCodecInfo()
{
FileInfo fi = new FileInfo(sourcePath);
switch (fi.Extension)
{
case ".bmp": return ImageCodecInfo.GetImageEncoders()[0];
case ".jpg":
case ".jpeg": return ImageCodecInfo.GetImageEncoders()[1];
case ".gif": return ImageCodecInfo.GetImageEncoders()[2];
case ".tiff": return ImageCodecInfo.GetImageEncoders()[3];
case ".png": return ImageCodecInfo.GetImageEncoders()[4];
default: return null;
}
}
private ImageFormat GetImageFormat()
{
FileInfo fi = new FileInfo(sourcePath);
switch (fi.Extension)
{
case ".jpg": return ImageFormat.Jpeg;
case ".bmp": return ImageFormat.Bmp;
case ".gif": return ImageFormat.Gif;
case ".png": return ImageFormat.Png;
case ".tiff": return ImageFormat.Tiff;
default: return ImageFormat.Png;
}
}
}
here is the code for resize the image
byte[] compressedBuffer = new ImageResizer().GetScaledImage(300000, FileName);
here 30000 shows the size, and the filename is the name of the file
One of the Bitmap class constructors takes an original image and a new size as an input:
Image uploadedImage = new Bitmap("Path/to/your/image.bmp");
Image resizedImage = new Bitmap(uploadedImage, new Size(100, 100));
In the first line of the sample you load the image. In the second line of the example you create a new object using the uploaded image and a new size.
Twain scan images to memory and it consumes too much memory. So I scanned images in batch, and compress images in memory. So source images should be released. how can I do it?
protected void OnTwainTransferReady()
{
if (TwainTransferReady == null)
return; // not likely..
List<ImageSource> imageSources = new List<ImageSource>();
ArrayList pics = tw.TransferPictures();
tw.CloseSrc();
EndingScan();
picnumber++;
for (int i = 0; i < pics.Count; i++) {
IntPtr imgHandle = (IntPtr)pics[i];
/*if (i == 0) { // first image only
imageSources.Add(DibToBitmap.FormHDib(imgHandle));
//Refresh(image1); // force a redraw
}*/
ImageSource src = DibToBitmap.FormHDib(imgHandle);
ImageSource temp = ImageHelper.compressImage(src);
src = null;
imageSources.Add(temp);
Win32.GlobalFree(imgHandle);
}
// for some reason the main window does not refresh properly - resizing of the window solves the proble.
// happens only with the Canon LIDE Scanner
// Suspected: some messages where eaten by Twain
TwainTransferReady(this, imageSources);
}
Generate the BitSource
public static BitmapSource FormHDib(IntPtr dibHandle)
{
BitmapSource bs = null;
IntPtr bmpPtr = IntPtr.Zero;
bool flip = true; // vertivcally flip the image
try {
bmpPtr = Win32.GlobalLock(dibHandle);
Win32.BITMAPINFOHEADER bmi = new Win32.BITMAPINFOHEADER();
Marshal.PtrToStructure(bmpPtr, bmi);
if (bmi.biSizeImage == 0)
bmi.biSizeImage = (uint)(((((bmi.biWidth * bmi.biBitCount) + 31) & ~31) >> 3) * bmi.biHeight);
int palettSize = 0;
if (bmi.biClrUsed != 0)
throw new NotSupportedException("DibToBitmap: DIB with pallet is not supported");
// pointer to the beginning of the bitmap bits
IntPtr pixptr = (IntPtr)((int)bmpPtr + bmi.biSize + palettSize);
// Define parameters used to create the BitmapSource.
PixelFormat pf = PixelFormats.Default;
switch (bmi.biBitCount) {
case 32:
pf = PixelFormats.Bgr32;
break;
case 24:
pf = PixelFormats.Bgr24;
break;
case 8:
pf = PixelFormats.Gray8;
break;
case 1:
pf = PixelFormats.BlackWhite;
break;
default: // not supported
throw new NotSupportedException("DibToBitmap: Can't determine picture format (biBitCount=" + bmi.biBitCount + ")");
// break;
}
int width = bmi.biWidth;
int height = bmi.biHeight;
int stride = (int)(bmi.biSizeImage / height);
byte[] imageBytes = new byte[stride * height];
//Debug: Initialize the image with random data.
//Random value = new Random();
//value.NextBytes(rawImage);
if (flip) {
for (int i = 0, j = 0, k = (height - 1) * stride; i < height; i++, j += stride, k -= stride)
Marshal.Copy(((IntPtr)((int)pixptr + j)), imageBytes, k, stride);
} else {
Marshal.Copy(pixptr, imageBytes, 0, imageBytes.Length);
}
int xDpi = (int)Math.Round(bmi.biXPelsPerMeter * 2.54 / 100); // pels per meter to dots per inch
int yDpi = (int)Math.Round(bmi.biYPelsPerMeter * 2.54 / 100);
// Create a BitmapSource.
bs = BitmapSource.Create(width, height, xDpi, yDpi, pf, null, imageBytes, stride);
Win32.GlobalUnlock(pixptr);
Win32.GlobalFree(pixptr);
} catch (Exception ex) {
string msg = ex.Message;
}
finally {
// cleanup
if (bmpPtr != IntPtr.Zero) { // locked sucsessfully
Win32.GlobalUnlock(dibHandle);
}
}
Win32.GlobalUnlock(bmpPtr);
Win32.GlobalFree(bmpPtr);
return bs;
}
Compress Image in Memory
internal static System.Windows.Media.ImageSource compressImage(System.Windows.Media.ImageSource ims)
{
using (MemoryStream stream = new MemoryStream())
{
using (MemoryStream outS = new MemoryStream())
{
BitmapSource bs = ims as BitmapSource;
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
BitmapFrame bf = BitmapFrame.Create(bs);
//encoder.Frames.Add(BitmapFrame.Create(image1.Source));
encoder.Frames.Add(bf);
encoder.Save(stream);
stream.Flush();
try
{
// Read first frame of gif image
using (MagickImage image = new MagickImage(stream))
{
image.Quality = 75;
image.Resize(new Percentage(0.65));
image.Density = new Density(200, DensityUnit.PixelsPerInch);
image.Write(outS);
image.Dispose();
}
stream.Close();
stream.Dispose();
BitmapImage bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
outS.Position = 0;
bitmap.StreamSource = outS;
//
bitmap.EndInit();
//bitmap.Freeze();
outS.Flush();
outS.Close();
outS.Dispose();
ims = null;
return bitmap;
}
catch (Exception e)
{
return null;
}
}
}
}
DrawImage method of Graphics class is not creating high quality images. In this method I am splitting a master image into multiple image but the code generating first image in very low quality. But it is generating full black images for remaining height.
public static Bitmap[] Split(byte[] ByteImage)
{
// MasterImage: there is no problem in master image. it is saving it in good quality.
MemoryStream ms = new MemoryStream(ByteImage);
System.Drawing.Image MasterImage = System.Drawing.Image.FromStream(ms);
MasterImage.Save(HttpContext.Current.Server.MapPath("../../../App_Shared/Reports/Temp/MasterImage.Bmp"), ImageFormat.Bmp);
//Split master image into multiple image according to height / 1000
Int32 ImageHeight = 1000, ImageWidth = MasterImage.Width, MasterImageHeight = MasterImage.Height;
int PageCount = 0;
Int32 TotalPages = MasterImage.Height / 1000;
Bitmap[] imgs = new Bitmap[TotalPages];
for (int y = 0; y + 1000 < MasterImageHeight; y += 1000, PageCount++)
{
imgs[PageCount] = new Bitmap(ImageWidth, ImageHeight, PixelFormat.Format32bppPArgb);
using (Graphics gr = Graphics.FromImage(imgs[PageCount]))
{
gr.CompositingQuality = CompositingQuality.HighQuality;
gr.InterpolationMode = InterpolationMode.HighQualityBicubic;
gr.SmoothingMode = SmoothingMode.HighQuality;
//First image now working with this code line
gr.DrawImage(MasterImage, new System.Drawing.Rectangle(0, y, ImageWidth, ImageHeight),new System.Drawing.Rectangle(0, 0, ImageWidth, ImageHeight), GraphicsUnit.Pixel); //new System.Drawing.Rectangle(new Point(0, y), new Size(ImageWidth, ImageHeight)));
//gr.DrawImage(MasterImage, new System.Drawing.Rectangle(0, y, ImageWidth, ImageHeight)); //new System.Drawing.Rectangle(new Point(0, y), new Size(ImageWidth, ImageHeight)));
string FilePath = HttpContext.Current.Server.MapPath("../../../App_Shared/Reports/Temp/Image" + PageCount.ToString() + ".bmp");
imgs[PageCount].Save(FilePath, System.Drawing.Imaging.ImageFormat.Bmp);
//Here it is saving images. I got first image with very poor quality but remaining in total balck color.
gr.Dispose();
}
}
return imgs;
}
As #HansPassant mentioned the source and target rectangle are reversed.
You could also change the structure of your splitting a bit so it could work a bit more flexible, and it might have a better readability at a later time.
class Program
{
static IList<Bitmap> SplitImage(Bitmap sourceBitmap, int splitHeight)
{
Size dimension = sourceBitmap.Size;
Rectangle sourceRectangle = new Rectangle(0, 0, dimension.Width, splitHeight);
Rectangle targetRectangle = new Rectangle(0, 0, dimension.Width, splitHeight);
IList<Bitmap> results = new List<Bitmap>();
while (sourceRectangle.Top < dimension.Height)
{
Bitmap pageBitmap = new Bitmap(targetRectangle.Size.Width, sourceRectangle.Bottom < dimension.Height ?
targetRectangle.Size.Height
:
dimension.Height - sourceRectangle.Top, PixelFormat.Format32bppArgb);
using (Graphics g = Graphics.FromImage(pageBitmap))
{
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;
g.DrawImage(sourceBitmap, targetRectangle, sourceRectangle, GraphicsUnit.Pixel);
}
sourceRectangle.Y += sourceRectangle.Height;
results.Add(pageBitmap);
}
return results;
}
static void Main(string[] args)
{
string sourceFilename = Environment.CurrentDirectory + #"\testimage.jpg";
Bitmap sourceBitmap = (Bitmap)Image.FromFile(sourceFilename);
var images = SplitImage(sourceBitmap, 79);
int len = images.Count;
for (int x = len; --x >= 0; )
{
var bmp = images[x];
string filename = "Images-" + x + ".bmp";
bmp.Save(Environment.CurrentDirectory + #"\" + filename, ImageFormat.Bmp);
images.RemoveAt(x);
bmp.Dispose();
Console.WriteLine("Saved " + filename);
}
Console.WriteLine("Done with the resizing");
}
}
This would also dynamically size the last image in case the page is less than your specified bitmap height at the end :)
I have doubts that this part of code causes memory leak:
public FileResult ShowCroppedImage(int id, int size)
{
string path = "~/Uploads/Photos/";
string sourceFile = Server.MapPath(path) + id + ".jpg";
MemoryStream stream = new MemoryStream();
var bitmap = imageManipulation.CropImage(sourceFile, size, size);
bitmap.Save(stream, System.Drawing.Imaging.ImageFormat.Jpeg);
Byte[] bytes = stream.ToArray();
return File(bytes, "image/png");
}
How could I make a test to see if this piece of code is the cause?
EDIT:
public Image CropImage(string sourceFile, int newWidth, int newHeight)
{
Image img = Image.FromFile(sourceFile);
Image outimage;
int sizeX = newWidth;
int sizeY = newHeight;
MemoryStream mm = null;
double ratio = 0;
int fromX = 0;
int fromY = 0;
if (img.Width < img.Height)
{
ratio = img.Width / (double)img.Height;
newHeight = (int)(newHeight / ratio);
fromY = (img.Height - img.Width) / 2;
}
else
{
ratio = img.Height / (double)img.Width;
newWidth = (int)(newWidth / ratio);
fromX = (img.Width - img.Height) / 2;
}
if (img.Width == img.Height)
fromX = 0;
Bitmap result = new Bitmap(sizeX, sizeY);
//use a graphics object to draw the resized image into the bitmap
Graphics grPhoto = Graphics.FromImage(result);
//set the resize quality modes to high quality
grPhoto.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
grPhoto.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
grPhoto.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
//draw the image into the target bitmap
//now do the crop
grPhoto.DrawImage(
img,
new System.Drawing.Rectangle(0, 0, newWidth, newHeight),
new System.Drawing.Rectangle(fromX, fromY, img.Width, img.Height),
System.Drawing.GraphicsUnit.Pixel);
// Save out to memory and get an image from it to send back out the method.
mm = new MemoryStream();
result.Save(mm, System.Drawing.Imaging.ImageFormat.Jpeg);
img.Dispose();
result.Dispose();
grPhoto.Dispose();
outimage = Image.FromStream(mm);
return outimage;
}
I would write it as
public FileResult ShowCroppedImage(int id, int size)
{
string path = "~/Uploads/Photos/";
string sourceFile = Server.MapPath(path) + id + ".jpg";
using (MemoryStream stream = new MemoryStream())
{
using (Bitmap bitmap = imageManipulation.CropImage(sourceFile, size, size))
{
bitmap.Save(stream, System.Drawing.Imaging.ImageFormat.Jpeg);
Byte[] bytes = stream.ToArray();
return File(bytes, "image/png");
}
}
}
to ensure that stream.Dispose & bitmap.Dispose are called.
Might want to call stream.dispose(); after Byte[] bytes = stream.ToArray();.
Given the question was how to detect memory leaks/usage, I'd recommend writing a method that calls your function recording the memory usage before and after:
public void SomeTestMethod()
{
var before = System.GC.GetTotalMemory(false);
// call your method
var used = before - System.GC.GetTotalMemory(false);
var unreclaimed = before - System.GC.GetTotalMemory(true);
}
Before will measure the memory usage before your function runs. The used variable will hold how much memory your function used before the garbage collector was run and unreclaimed will tell you how many bytes your function used even after trying to clean up your objects.
I suspect used will be high and unreclaimed will not - putting a using around your memory stream as the other posters suggest should make them closer although bear in mind you still have a byte array holding on to memory.