How to load image in RichTextBox more effective? - c#

I am drag and drop a heavy image (95729 kb) in my RichTextBox. But memory usage is so incomprehensible: Why it stores more than 700 mb?
My Drag and Drop code:
private void RtbEditor_PreviewDrop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
if (files != null && files.Length > 0)
{
foreach (var file in files)
{
// Filter out non-image files.
if (IsValidImageFile(file))
{
BitmapImage bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.UriSource = new Uri(file, UriKind.Absolute);
bitmap.EndInit();
Image image = new Image();
image.Source = bitmap;
var container = new InlineUIContainer(image, rtbEditor.CaretPosition);
rtbEditor.CaretPosition = container.ElementEnd;
}
}
}
}
}
For image check, I took only image header: Check only header What am I doing wrong?

This heavy image probably has an enormous resolution and uses a format like PNG or JPEG. These formats compress the pixel data to make the final file size smaller. However, in order to render an image, the renderer needs to decompress the image into actual pixels with (A)RGB values.
An uncompressed 3000x3000 image with 32 bits per pixel weighs in at ~ 36 MB. So your image must be even larger.
Why do you want to render such a large image in a textbox anyway?

You definitely don't need that much of pixels. Just set BitmapImage.DecodePixelHeight or BitmapImage.DecodePixelHeight to appropriate value between BitmapImage.BeginInit() and BitmapImage.BeginInit(). And don't set both unless you don't care about original aspect ratio.

Related

Optimization of image resize process

I need to load several images (up to 1000) and display it in a list (longest side of the image max. 250 px).
Since many of them are raw format images (CR2 / AWR), I cannot open them simply by
var i = new Bitmap(x);
Therefore I am opening it as BitmapImage.
Secondly, size is really an issue, that's why I have to shrink it to a kind of thumbnail.
Since I cannot read the height and width during BitmapImage init process, I am loading it first, and then shrink it.
The following code does the trick, but it is awfully slow and consumes a hell of memory.
If I would ignore the "longest" side and shrink the width (or height) inside the init part, memory consumption is reduced to about 10 %.
Can somebody help me out to optimize it?
public BitmapSource getImage(string fileName, double width, double height)
{
// Read and resize image
BitmapImage tmpImage = new BitmapImage();
tmpImage.BeginInit();
tmpImage.CacheOption = BitmapCacheOption.OnLoad;
tmpImage.UriSource = new Uri(fileName);
tmpImage.EndInit();
if (tmpImage.Width > tmpImage.Height)
{
tmpImage.DecodePixelWidth = (int)width;
}
else
{
tmpImage.DecodePixelHeight = (int)height;
}
return tmpImage;
}

Where does this quality loss on Images come from?

in my Winforms application which is connected to a database via Linq to SQL I am saving images (always *.png) to a table which looks like this:
CREATE TABLE [dbo].[Images] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[Bild] IMAGE NOT NULL,
PRIMARY KEY CLUSTERED ([Id] ASC)
);
Before I can store a picture I have to convert it to byte[] and this is how I do it:
public static byte[] ImageToByteArray(System.Drawing.Image imageIn)
{
using (MemoryStream ms = new MemoryStream())
{
imageIn.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
return ms.ToArray();
}
}
Afterwards if I want to load this very same image to a PictureBox in my application I convert it back with this method:
public static Image ByteArrayToImage(byte[] byteArrayIn)
{
using (MemoryStream ms = new MemoryStream(byteArrayIn))
{
Image returnImage = Image.FromStream(ms);
return returnImage;
}
}
It actually works, the only problem appears when I try to display an Image from the database in a Picturebox.
So when I load this Image to the database:
and later I try to display it. It suddenly looks like this:
I already tried all the possible SizeMode settings for the PictureBox (Normal, Stretchimage, AutoSize,CenterImage,Zoom) and it still looks like this.
Also here is how I load the Images from the database to the pictureBox:
First I retrieve the all the Images belonging to a set via id:
public static ImageList GetRezeptImages(int rezeptId)
{
using (CookBookDataContext ctx = new CookBookDataContext(ResourceFile.DBConnection))
{
IEnumerable<RezeptBilder> bilder = from b in ctx.RezeptBilders where b.FKRezept == rezeptId select b;
ImageList imageList = new ImageList();
foreach(RezeptBilder b in bilder)
{
imageList.Images.Add(Helper.ByteArrayToImage(b.Bild.ToArray()));
}
return imageList;
}
}
Also in my application I have a datagridview where Id's are stored in the first column. So when I want to retrieve any Images belonging to the set I do it like this:
private void dgvRezeptListe_CellClick(object sender, DataGridViewCellEventArgs e)
{
pbRezeptBild.Image = DBManager.GetRezeptImages(Int32.Parse(dgvRezeptListe.SelectedRows[0].Cells[0].Value.ToString())).Images[0];
}
When loaded from a local directory the Image looks fine in the pictureBox. I also tried to convert the initial picture to binary and back (without loading it to the database), it still looked fine when displayed in the pictureBox.
There is something else I realized while debugging the Image which came from the database. When looking into the ImageSize, width and height had both the value 16. This is weird because the original image has totally different dimensions.
Any ideas?
It appears that ImageList is converting your images to MemoryBmp's when you are adding them in GetRezeptImages to the list.
I am not aware as to why it would do that, but that is the reason for your loss of quality in the image. As you also realised, the image is converted to a 16x16 image and then when that is resized in your PictureBox to the original size, it just looks more cheesy.
Edit:
From TaW's comment: The ImageList collection cannot handle varying sized images, so it is converting all the images to a common size. Since there is no size set, it seems it defaults to 16x16.
I would recommend that you change the GetRezeptImages method to return a List<Image> instead of an ImageList and use that accordingly to show the images.
Alternatively, if you will always use the GetRezeptImages method in the same way that you showed in your question, you can change it to always just return the first image in an Image object and entirely throw away all the lists.
ImageList is nice for what it can do: Store lots of Images without losing GDI resources.
But what it can't do is what you probably need: Store images of varying sizes & proportions.
You can and should set it Imagesize and ColorDepth properties (look at the defaults, that are clearly meant for use as a StateImageList; 16x16px and 8bit depth is even bad for a LargeImageList..) but you can't use it if your images need to have different sizes & proportions..
If they don't, that is if they can share at least their proportions, just fix the ImageList by picking a nice size and you're set! All Images you add to the ImageList will automatically be scaled to that Size and converted to the ColorDepth.
Otherwise replace it by a List<Image> or List<Bitmap>..!
If you know the common size and ColorDepth of your Images you can do that:
using (CookBookDataContext ctx = new CookBookDataContext(ResourceFile.DBConnection))
{
IEnumerable<RezeptBilder> bilder =
from b in ctx.RezeptBilders where b.FKRezept == rezeptId select b;
ImageList imageList = new ImageList();
imageList.ColorDepth = ColorDepth.Depth24Bit; //
imageList.ImageSize = yourImageSize; //
foreach(RezeptBilder b in bilder)
{
imageList.Images.Add(Helper.ByteArrayToImage(b.Bild.ToArray()));
}
return imageList;
}
You can either not use an ImageList but a List<Image> or List<Bitmap> instead or make
them - that is: Trim them to the same proportion, basically not much more than a few extra lines..:
Bitmap expandCanvas(Bitmap bmp, Size size)
{
float f1 = 1f * bmp.Width / bmp.Height;
float f2 = 1f * size.Width / size.Height;
Size newSize = size;
if (f1 > f2) newSize = new Size(bmp.Width, (int)(bmp.Height * f1));
else if (f1 < f2) newSize = new Size((int)(bmp.Width / f1), bmp.Height);
Bitmap bmp2 = new Bitmap(newSize.Width, newSize.Height);
bmp2.SetResolution(bmp.HorizontalResolution, bmp.VerticalResolution);
Rectangle RDest = new Rectangle(Point.Empty, bmp.Size);
Rectangle RTgt = new Rectangle(Point.Empty, newSize);
using (Graphics G = Graphics.FromImage(bmp2))
{
G.DrawImage(bmp, RDest, RDest, GraphicsUnit.Pixel);
}
return bmp2;
}
This routine expands the image canvas size with transparent pixels to the right or bottom without scaling the pixels, so it should stay losslessly crisp.
Also You might think about converting it to a string64 instead of byte[]. The code is a little cleaner and once it's a string it can be dropped in just about any file because it's just text.

How can i add two animated gif's on one bitmap?

I have two animated gifs i created.
I want to add them to a new bitmap side by side so i will see both animated gifs animation.
Not as stil image but two animation side by side.
This code is in the top of form1 im using now:
public static class BitmapExtensions
{
public static Bitmap DoubleBitmap(this Bitmap bm)
{
Bitmap bitmap = new Bitmap(bm.Width * 2, bm.Height);
using (Graphics g = Graphics.FromImage(bitmap))
{
g.DrawImage(bm, Point.Empty);
g.DrawImage(bm, new Point(bm.Width, 0));
return bitmap;
}
}
public static Bitmap AppendBitmap(this Bitmap bm, Bitmap rightBitmap)
{
Bitmap bitmap = new Bitmap(bm.Width + rightBitmap.Width, Math.Max(bm.Height, rightBitmap.Height));
using (Graphics g = Graphics.FromImage(bitmap))
{
g.DrawImage(bm, Point.Empty);
g.DrawImage(rightBitmap, new Point(bm.Width, 0));
return bitmap;
}
}
}
Then i use it like this:
private void CreateNewImage(string DirOfUrls)
{
List<string> files = Directory.GetFiles(DirOfUrls, "RainImage*.*").ToList();
List<string> files1 = Directory.GetFiles(DirOfUrls, "SatelliteImage*.*").ToList();
Bitmap bmp = new Bitmap(#"d:\localpath\RainMapGif");//files1[i]);
Bitmap bmp1 = new Bitmap(#"d:\localpath\SatelliteMapGif");//files[i]);
//Use it
//Double the same image
Bitmap doubledBitmap = bmp1.DoubleBitmap();
//Append new image
Bitmap appendedBitmap = bmp1.AppendBitmap(bmp);
appendedBitmap.Save(#"d:\localpath\newbitmapGif", System.Drawing.Imaging.ImageFormat.Gif);
}
RainMapGif and SatelliteMapGif are animated gif's.
But when i tried to do it this way i get one new bitmap with two stills images and not two animtions of the two animated gifs.
How can i add both animated gifs to one bitmap and when i open the bitmap on internet explorer for example i will see both animations moving of the two gifs side by side ?
EDIT**
This is how i used it before :
private void CreateNewImage(string DirOfUrls)
{
int newImageCounter = 0;
List<string> files = Directory.GetFiles(DirOfUrls, "RainImage*.*").ToList();
List<string> files1 = Directory.GetFiles(DirOfUrls, "SatelliteImage*.*").ToList();
for (int i = 0; i < files.Count; i++)
{
if (newImageCounter == 9)
{
CreateNewGif(DirOfUrls);
//break;
}
Bitmap bmp = new Bitmap(files1[i]);
Bitmap bmp1 = new Bitmap(files[i]);
//Use it
//Double the same image
Bitmap doubledBitmap = bmp1.DoubleBitmap();
//Append new image
Bitmap appendedBitmap = bmp1.AppendBitmap(bmp);
appendedBitmap.Save(#"d:\localpath\newbitmap" + newImageCounter.ToString("D6"), System.Drawing.Imaging.ImageFormat.Gif);
newImageCounter++;
}
So i have 9 images of both of them the RainMap images and the SatelliteMap images.
And the rest of the SatelliteMap images are singles.
Then im using CreateNewGif:
private void CreateNewGif(string urlsdirs)
{
List<string> files = Directory.GetFiles(urlsdirs, "RainImage*.*").ToList();
List<string> files1 = Directory.GetFiles(urlsdirs, "SatelliteImage*.*").ToList();
List<string> test = files;
test.RemoveRange(0, files1.Count);
List<string> newbitmap = Directory.GetFiles(localdir, "newbitmap*.*").ToList();
for (int i = 0; i < test.Count; i++)
{
newbitmap.Add(test[i]);
}
uf.MakeGIF(newbitmap, localdir + "newbitmapGif", 50, true);
}
And make new animated gif:
uf.MakeGIF(newbitmap, localdir + "newbitmapGif", 50, true);
But the new animated gif is not good since the rain images ending before the satellite images so the new animated gif show both animation and after 9 frames only one is continue.
How can i make that the one with the 9 images will keep continue animated over and over again and the second one will keep animated untill the end ?
This is the problem veljkoz wrote about before. One animation count is shorter then the other one. But how can i solve it ?
Only gif can have animation. Bitmap doesn't and never will.
Furthermore, there's the issue of extracting all of the gif slides (see this answer), putting them side by side in another gif (see this answer), and deciding what will you do if the slide count isn't a match (which would most probably be the case).
Personally, I would go with what #Mike W is suggesting in the comment...
Edit:
You have to have in mind that the resulting gif will have a single number of frames after which it repeats itself. So, both gifs you're trying to add have to somehow fit there. There's only a few options here:
Repeat inserting all frames of both gifs until they reach at the end at the same time (e.g. if one has 6 frames and the other 9 - the result will have 18 frames. The the first one would repeat itself 3 times, the other 2). This is (obviously) prolonging the length & size of the gif, which is probably not something you want, but it would give you best results. When the numbers are prime numbers you'd get the worst count - e.g. 53 & 59 frame gifs when joined will last for 3127 frames.
Change the timing of the frames so they end at the same time. This would of course speed-up / slow down parts of the gifs. (e.g. 6&9 frames gifs: the result has 9 slides - you could repeat the first 3 slides of 1st gif to last two frames - which means 1st slide will last 2 frames (#1 & #2), second one also (#3 & #4) as well as third (#5 & #6). Then copy the rest of the slides - frame 4 displayed at #7, 5->#8, 6->#9 and that's the end).
Remove some of the slides - shorten the gifs by removing some of the slides so the count matches...

Resize Image of any size to fixed dimension using C# ASP.Net web form

I have done image resizing while allowing user to upload a specific size image and then crop them to different dimension i have also used jCrop in project to allow users to upload a image of specific size and then select the image area & crop it accordingly.
In new project i have a requirement where user can upload any size image which is at least larger than 500Px in width and then i have to allow user to select the part of image using jCrop and then save image in different dimension of 475x313 , 310x205 while maintaining the aspect ration.
I can do it with if i allow the used to upload a fixed size image but i am not sure how i can handle variable size image.
I also need to display the image uploaded before cropping in a fixed size box.. let us say 300x200. in this area i have to allow the user to select the part of the image before i can crop.
Issue i am facing is how to handle variable length image and show it is a fixed image box of 300x200px.
I wrote an article on using jCrop with dynamically-resized uploaded images, which seems to be what you're needing.
If you're looking for an open-source ASP.NET control that does it for you, check out cropimage.net.
Want to going to by programmatically than you can try this :
if you are using file upload for upload images
string path = Path.GetFileName(fileuploaderID.PostedFile.FileName);
ConvertThumbnails(width, height, fileuploaderID.FileBytes, path);
your function
public void ConvertThumbnails(int width, int height, byte[] filestream, string path)
{
// create an image object, using the filename we just retrieved
var stream = new MemoryStream(filestream);
System.Drawing.Image image = System.Drawing.Image.FromStream(stream);
try
{
int fullSizeImgWidth = image.Width;
int fullSizeImgHeight = image.Height;
float imgWidth = 0.0F;
float imgHeight = 0.0F;
imgWidth = width;
imgHeight = height;
Bitmap thumbNailImg = new Bitmap(image, (int)imgWidth, (int)imgHeight);
MemoryStream ms = new MemoryStream();
// Save to memory using the Jpeg format
thumbNailImg.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
// read to end
byte[] bmpBytes = ms.GetBuffer();
item.Attachments.Add(path, bmpBytes);
thumbNailImg.Dispose();
ms.Close();
}
catch (Exception)
{
image.Dispose();
}
}

save png image with custom quality or color depth in C# [duplicate]

I am supposed to write a program that gets some PNG images from the user, does some simple edits like rotation and saves them inside a JAR file so that it can use the images as resources. The problem is when I open, say an 80kb image and then save it with C#, I get an image with the same quality but for 130kb space. And because it has to go inside a J2ME jar file I really need lower sizes, at least the original size. I tried the code below but later found out that it only works for Jpeg images.
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();
int j = 0;
for (j = 0; j < codecs.Length; j++)
{
if (codecs[j].MimeType == "image/png") break;
}
EncoderParameter ratio = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 10L);
EncoderParameters CodecParams = new EncoderParameters(1);
CodecParams.Param[0] = ratio;
Image im;
im = pictureBox1.Image;
im.Save(address , codecs[j], CodecParams);
This is where the image is loaded to a picture box:
private void pictureBox1_DoubleClick(object sender, EventArgs e)
{
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
string address = openFileDialog1.FileName;
address.Replace("\\", "\\\\");
Image im = Image.FromFile(address);
pictureBox1.Image = im;
}
}
And this is where it's being saved back with no edits:
private void generateToolStripMenuItem_Click(object sender, EventArgs e)
{
if (folderBrowserDialog1.ShowDialog() == DialogResult.OK)
{
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();
int j = 0;
for (j = 0; j < codecs.Length; j++)
{
if (codecs[j].MimeType == "image/png") break;
}
EncoderParameter ratio = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 10L);
EncoderParameters CodecParams = new EncoderParameters(1);
CodecParams.Param[0] = ratio;
string address = folderBrowserDialog1.SelectedPath;
address = address + "\\";
address.Replace("\\", "\\\\");
Image im;
im = pictureBox1.Image;
im.Save(address + imageFileNames[1], codecs[j], CodecParams);
Note: imageFileNames[] is just a string array that has some of the the file names to which the images must be saved with.
Any ideas will be appreciated and thanks in advance.
Sorry for bringing up an old question, but I were solving the same problem today and found this page on MSDN: "Listing Parameters and Values for All Encoders". I hope it might be useful to refer to it there.
The article contains a sample program which outputs EncoderParameters supported by stock GDI+ image encoders, and PNG encoder supports no parameters according to it.
I'm not sure thought if one can take this for granted, or in some future version of gdiplus.dll the PNG encoder might grow more support, and so one is supposed to check the encoder's capabilities at runtime.
In any case applying some explicit transformation on a source image appears to be more fruitful approach; I did not explore it yet though.
I have taken 4 different PNGs, ranging in sizes from 2KB to 2.6MB. Using your code, I load them up and save them out. I do not modify the image by rotating or flipping. All 4 PNGs, when re-saved, have exactly the same size.
In addition, I have taken the 2.6MB PNG, opened it in Photoshop and saved two copies of it, one interlaced (reduced to 2.06MB), one non-interlaced (reduced to 1.7MB.) I then took each of those, ran them through your code and saved them. The resulting sizes of both were back to the original 2.6MB.
So, my guess is that the original images were created with a piece of software (like photoshop) and that piece of software has an optimized compression algorithm it is using for the PNG spec that .NET does not employ.
I've messed around with the EncoderParameter for Compression, but it seems to have no impact.

Categories

Resources