How to thumbnail faster in c# - c#

I'm trying to thumb an image as fast as possible regardless of the usage of resources to be used in my ImageList and listview and this is currently how i'm doing it but it seems to be slow:
public Image toThumbs(string file, int width, int height)
{
image = null;
aspectRatio = 1;
fullSizeImg = null;
try
{
fullSizeImg = Image.FromFile(file);
float w = fullSizeImg.Width;
float h = fullSizeImg.Height;
aspectRatio = w / h;
int xp = width;
int yp = height;
if (fullSizeImg.Width > width && fullSizeImg.Height > height)
{
if ((float)xp / yp > aspectRatio)
{
xp = (int)(yp * aspectRatio);
}
else
{
yp = (int)(xp / aspectRatio);
}
}
else if (fullSizeImg.Width != 0 && fullSizeImg.Height != 0)
{
xp = fullSizeImg.Width;
yp = fullSizeImg.Height;
}
image = new Bitmap(width, height);
graphics = Graphics.FromImage(image);
graphics.FillRectangle(Brushes.White, ((width - xp) / 2), (height - yp), xp, yp);
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
graphics.DrawImage(fullSizeImg, new Rectangle(((width - xp) / 2), (height - yp), xp, yp));
graphics.Dispose();
fullSizeImg.Dispose();
}
catch (Exception)
{
image = null;
}
return image;
}
I'm not sure if the computation is the one that is slowing down the thumbnailing or maybe the classes itself that are being used are slow, if that is the case then what other alternatives can be use maybe a different computation or i need to import other classes or is there a third party libraries that can be used or i need to do a dll import or something? Please help me.
Edit: Just found a solution here
http://www.vbforums.com/showthread.php?t=342386
it extracts a thumbnail from a file
without reading the whole file. I was able to reduce the time about 40% when i used this.

Your calculations happen in fractions of a second. The call to DrawImage is most likely the slowest part of this (as that one is doing the scaling).
If you're needing this thumbnail image exactly once then I don't see much room for improvement here. If you're calling that method on the same image more than once, you should cache the thumbnails.

I use this mechanism which seems to be very fast.
BitmapFrame bi = BitmapFrame.Create(new Uri(value.ToString()), BitmapCreateOptions.DelayCreation, BitmapCacheOption.OnDemand);
// If this is a photo there should be a thumbnail image, this is VERY fast
if (bi.Thumbnail != null)
{
return bi.Thumbnail;
}
else
{
// No thumbnail so make our own (Not so fast)
BitmapImage bi2 = new BitmapImage();
bi2.BeginInit();
bi2.DecodePixelWidth = 100;
bi2.CacheOption = BitmapCacheOption.OnLoad;
bi2.UriSource = new Uri(value.ToString());
bi2.EndInit();
return bi2;
}
Hope this helps.

Out of curiosity, have you tried the GetThumbnailImage method on System.Drawing.Bitmap? It might at least be worth comparing to your current implementation.

This may seem like to much of an obvious answer but have you tried just using Image.GetThumbnailImage()?
You don't get as much control over the quality of the result but if speed is your main concern?

Your thumbnail extraction that gets you the big speed up relies on the image having a thumbnail already embedded in it.
To speed up the original you might find that changing:-
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
to
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.Low;
Might help.

Related

Make sure that an image larger than 5 mb is reduced below that size c#

I'm using ImageProcessor to reduce the resolution or quality of an image, but I'm don't know how to make sure that the image resultant size it's below 5 megabytes. I tried setting the image dimensions to 3840-2160 but I want to use a better option.
Here it's my code:
private static byte[] redimensionImage(ref byte[] photoBytes)
{
var byteCuantity = ConvertBytesToMegabytes(photoBytes.Count());
ISupportedImageFormat format = new JpegFormat();
using (MemoryStream inStream = new MemoryStream(photoBytes))
{
using (MemoryStream outStream = new MemoryStream())
{
// Initialize the ImageFactory using the overload to preserve EXIF metadata.
using (ImageFactory imageFactory = new ImageFactory(preserveExifData: true))
{
// Load, resize, set the format and quality and save an image.
using (var imageProcessor = imageFactory.Load(inStream))
{
var originalHeight = imageProcessor.Image.Size.Height;
var originalWidth = imageProcessor.Image.Size.Width;
//calculate aspect ratio
var aspect = originalWidth / (float)originalHeight;
int newWidth, newHeight;
var dimenssionTooSmall = false;
if (originalWidth <= originalHeight && originalWidth < 100)
{
//calculate new dimensions based on aspect ratio
newHeight = (int)(100 / aspect);
var resizeLayer = new ResizeLayer(new Size(100, newHeight), ResizeMode.Min);
imageProcessor.Resize(resizeLayer);
dimenssionTooSmall = true;
}
else if (originalHeight < originalWidth && originalHeight < 100)
{
//calculate new dimensions based on aspect ratio
newWidth = (int)(100 / aspect);
var resizeLayer = new ResizeLayer(new Size(newWidth, 100), ResizeMode.Min);
imageProcessor.Resize(resizeLayer);
dimenssionTooSmall = true;
}
if (byteCuantity > 1 || dimenssionTooSmall)
{
//format.Quality = 6;
imageProcessor.Resize(new ResizeLayer(new Size(3840, 2160), ResizeMode.Min));
imageProcessor.Format(format);
imageProcessor.Save(outStream);
return outStream.ToArray();
}
else
{
return inStream.ToArray();
}
}
}
}
}
}
Thanks and regards.
Unfortunately there's no way you can really do this without reprocessing unless you're saving as bitmap.
When you save an image there are many compression processes that take place to store the image in each individual format (except bitmap which doesn't compress the image). Without actually going through the process itself you can't predict the file size.
You could potentially create your own lookup tables to act as a guideline by resizing a large sample of images in different formats and collecting the output sizes to give you a rough estimate for future processing.

Error saving resized image

I have the following code
int oswidth = 0;
int osheight = 0;
if (comboBox3.SelectedIndex == 0)
{
oswidth = Convert.ToInt32(textBox5.Text.ToString());
osheight = Convert.ToInt32(textBox6.Text.ToString());
}
else if (comboBox3.SelectedIndex == 1)
{
oswidth = 38 * Convert.ToInt32(textBox5.Text.ToString());
osheight = 38 * Convert.ToInt32(textBox6.Text.ToString());
}
Bitmap oldimg = new Bitmap(pictureBox3.Image);
Bitmap objBitmap = new Bitmap(oldimg, new Size(oswidth, osheight));
objBitmap.Save(pictureBox3.ImageLocation.ToString(), ImageFormat.Jpeg);
The problem is when the selected index is 0 it works fine
but when the selected index is 1 i get a error "Parameter is not valid."
i tried different images but same error. is it the multiply by 32 thing
The Parameter is not valid error message when trying to create a Bitmap usually means that you are trying to allocate too much memory to it. The bitmap requires bit-depth*width*height/8 bytes of contiguous memory, and there just isn't enough available to satisfy that.
In this case, it looks like it's because you're multiplying its dimensions by 38 (and therefore multiplying the size in memory by 38^2).
You could utilize the following method:
private static void ResizeImage(string file, double vscale, double hscale, string output)
{
using(var source = Image.FromFile(file))
{
var width = (int)(source.Width * vscale);
var height = (int)(source.Height * hscale);
using(var image = new Bitmap(width, height, PixelFormat.Format24bppRgb))
using(var graphic = Graphics.FromImage(image))
{
graphic.SmoothingMode = SmoothingMode.AntiAlias;
graphic.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphic.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphic.DrawImage(source, new Rectangle(0, 0, width, height));
image.Save(output);
}
}
}
You can tailor this however you'd like, but it should meet your needs.
Important: The reason vscale and hscale are separate is to not follow scaling. You can easily combine them so you can scale accordingly. The other thing to remember, is your using a value of 32. Try using a value of .32 which will treat it more like a percent, which will scale. Also it won't increase the memory drastically causing your error.

Get Whole Black Result when Resizing WriteableBitmap

I found that my problem lies in a method I used to resize the picture:
Here's the code:
private WriteableBitmap ResizeImage(BitmapImage original, double destWidth, double destHeight)
{
Image image = new Image()
{
Source = original,
Stretch = Stretch.UniformToFill
};
image.UpdateLayout();
int origWidth = original.PixelWidth;
int origHeight = original.PixelHeight;
ScaleTransform st = new ScaleTransform();
st.ScaleX = destWidth / (double)origWidth;
st.ScaleY = destHeight / (double) origHeight;
WriteableBitmap result = new WriteableBitmap((int)destWidth, (int)destHeight);
result.Render(image, st);
result.Invalidate();
return result;
}
I tested my code under 2 circumstances:
pass picture from "Camera Roll"
pass picture from other albums
My code will work on "Camera Roll" pictures, but it will return me a whole black result for the pictures loaded from other albums.
In either of these circumstances, despite the bitmap is whole black or not, this method will return me the bitmaps correct width and height.
For these two scenario, I'm using the same method to load pictures, but why only pictures from camera roll can be displayed but those who come from other albums cannot?
I know the WriteableBitmapEx library has the method to do resize perfectly. But I just curious about why my method won't work? Could any one help me with that?
Try the following:
private static BitmapSource ResizeImage(BitmapImage original, int destWidth, int destHeight)
{
if (original == null) return null;
if (destWidth == original.PixelWidth && destHeight == original.PixelHeight) return original;
return new TransformedBitmap(original, new ScaleTransform((double)destWidth / original.PixelWidth, (double)destHeight / original.PixelHeight));
}
I'm not sure if TransformedBitmap is available on Windows Phone...
By the way, you're performing an integer division:
st.ScaleX = destWidth / origWidth;
st.ScaleY = destHeight / origHeight;
For instance, if destWidth=150 and origWidth=300 then st.ScaleX will be 0.0 (not 0.5).

What's a good way to store project related pictures?

I'm trying to get an array of PictureBox to display a list of pictures (in png file format).
I tried to use the .NET ImageList control but it insists in re-sizing my pictures. It also does not support transparent background of those png files.
I also tried to use the Assembly to retrieve my files like this:
_imageStream = _assembly.GetManifestResourceStream("MyNamespace.MyImage.png");
but the code does not return me any resource files nor does it throw any run-time error.
My question is, is there any other ways to do this? Or better yet, can I somehow make the ImageList control to NOT alter my picture? Thanks.
You can try something like this although I am not sure that this is the best one or not:-
Assembly ambly = Assembly.LoadFile(pathToDll);
or
BitMap bitMap;
// where "ns" is the default namespace of the resource project
using (Stream resourceStream = ambly.GetManifestResourceSream("ns.image.jpg"))
{
bitMap = BitMap.FromStream(resourceStream);
}
An example:-
interface IThemeResourceProvider
{
Stream LoadBigLogo();
Stream LoadSmallLogo();
}
Then implement that interface in your resource library
public class ThemeResourceProvider : IThemeResourceProvider
{
public Stream LoadBigLogo()
{
Assembly ambly = Assembly.GetExecutingAssembly();
return ambly.GetManifestResourceStream("namespace.image.jpg");
}
(...)
}
Finally, instead of loading the resource directly in your main application, you instantiate the IThemeResourceProvider found in the resource library
Assembly assembly = Assembly.LoadFile(pathToDll);
var results = from type in assembly.GetTypes()
where typeof(IThemeResourceProvider).IsAssignableFrom(type)
select type;
Now you have an IEnumerable in that list. Typically, you'd only have one, but using this approach you could also host multiple sets of resources, and implement multiple IThemeResourceProviders in the same resource dll. You could e.g. identify each IThemeResourceProvider with a name, either as a property, or using a custom [Attribute] decoration on your various implementations. I'll leave the rest up to you to figure out.
But here's how to instantiate the IThemeResourceProviders in your list
foreach (var providerType in results)
{
var constructorInfo = providerType.GetConstructor(Type.EmptyTypes);
IThemeResourceProvider provider = constructorInfo.Invoke(null);
}
And finally, using one of these providers to get a bitmap:
BitMap bitMap;
using (Stream resourceStream = provider.LoadBigLogo())
{
bitMap = BitMap.FromStream(resourceStream);
}
This is the code that I got from someone and it's worked well for me!
private void SetImage(PictureBox pb) {
try {
Image img = pb.Image;
Size imgSize = GenerateImageDimensions( img.Width, img.Height, pb.Width, pb.Height );
Bitmap finalImg = new Bitmap( img, imgSize.Width, imgSize.Height );
Graphics gfx = Graphics.FromImage( img );
gfx.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
pb.Image = null;
pb.SizeMode = PictureBoxSizeMode.AutoSize;
pb.Image = finalImg;
} catch(Exception ex) {
}
}
public Size GenerateImageDimensions(int currW, int currH, int destW, int destH) {
//double to hold the final multiplier to use when scaling the image
double multiplier = 0;
//string for holding layout
string layout;
//determine if it's Portrait or Landscape
if(currH > currW) layout = "portrait";
else layout = "landscape";
switch(layout.ToLower()) {
case "portrait":
//calculate multiplier on heights
if(destH > destW) {
multiplier = (double) destW / (double) currW;
} else {
multiplier = (double) destH / (double) currH;
}
break;
case "landscape":
//calculate multiplier on widths
if(destH > destW) {
multiplier = (double) destW / (double) currW;
} else {
multiplier = (double) destH / (double) currH;
}
break;
}
//return the new image dimensions
return new Size( (int) (currW * multiplier), (int) (currH * multiplier) );
}
EDIT: Full disclosure all my images are jpg so I have no clue how this will hand transparent backgrounds.
EDIT TWO: Also you will need to adjust the pb.SizeMode to fit your needs. The way I did it was to set a max size for the PictureBox and it's worked well.

Fill the holes in emgu cv

How can I fill the holes in binary image in emgu cv?
In Aforge.net it's easy, use Fillholes class.
Thought the question is a little bit old, I'd like to contribute an alternative solution to the problem.
You can obtain the same result as Chris' without memory problem if you use the following:
private Image<Gray,byte> FillHoles(Image<Gray,byte> image)
{
var resultImage = image.CopyBlank();
Gray gray = new Gray(255);
using (var mem = new MemStorage())
{
for (var contour = image.FindContours(
CHAIN_APPROX_METHOD.CV_CHAIN_APPROX_SIMPLE,
RETR_TYPE.CV_RETR_CCOMP,
mem); contour!= null; contour = contour.HNext)
{
resultImage.Draw(contour, gray, -1);
}
}
return resultImage;
}
The good thing about the method above is that you can selectively fill holes that meets your criteria. For example, you may want to fill holes whose pixel count (count of black pixels inside the blob) is below 50, etc.
private Image<Gray,byte> FillHoles(Image<Gray,byte> image, int minArea, int maxArea)
{
var resultImage = image.CopyBlank();
Gray gray = new Gray(255);
using (var mem = new MemStorage())
{
for (var contour = image.FindContours(
CHAIN_APPROX_METHOD.CV_CHAIN_APPROX_SIMPLE,
RETR_TYPE.CV_RETR_CCOMP,
mem); contour!= null; contour = contour.HNext)
{
if ( (contour.Area < maxArea) && (contour.Area > minArea) )
resultImage.Draw(contour, gray, -1);
}
}
return resultImage;
}
Yes there is a method but it's a bit messy as its based on cvFloodFill operation. Now all this algorithm is designed to do is fill an area with a colour until it reaches an edge similar to a region growing algorithm. To use this effectively you need to use a little inventive coding but I warn you this code is only to get you started it may require re-factoring to speed things up . As it stands the loop goes through each of your pixels that are less then 255 applies cvFloodFill checks what size the area is and then if it is under a certain area fill it in.
It is important to note that a copy of the image is made of the original image to be supplied to the cvFloodFill operation as a pointer is used. If the direct image is supplied then you will end up with a white image.
OpenFileDialog OpenFile = new OpenFileDialog();
if (OpenFileDialog.ShowDialog() == DialogResult.OK)
{
Image<Bgr, byte> image = new Image<Bgr, byte>(OpenFile.FileName);
for (int i = 0; i < image.Width; i++)
{
for (int j = 0; j < image.Height; j++)
{
if (image.Data[j, i, 0] != 255)
{
Image<Bgr, byte> image_copy = image.Copy();
Image<Gray, byte> mask = new Image<Gray, byte>(image.Width + 2, image.Height + 2);
MCvConnectedComp comp = new MCvConnectedComp();
Point point1 = new Point(i, j);
//CvInvoke.cvFloodFill(
CvInvoke.cvFloodFill(image_copy.Ptr, point1, new MCvScalar(255, 255, 255, 255),
new MCvScalar(0, 0, 0),
new MCvScalar(0, 0, 0), out comp,
Emgu.CV.CvEnum.CONNECTIVITY.EIGHT_CONNECTED,
Emgu.CV.CvEnum.FLOODFILL_FLAG.DEFAULT, mask.Ptr);
if (comp.area < 10000)
{
image = image_copy.Copy();
}
}
}
}
}
The "new MCvScalar(0, 0, 0), new MCvScalar(0, 0, 0)," are not really important in this case as you are only filling in results of a binary image. YOu could play around with other settings to see what results you can achieve. "if (comp.area < 10000)" is the key constant to change is you want to change what size hole the method will fill.
These are the results that you can expect:
Original
Results
The problem with this method is it's extremely memory intensive and it managed to eat up 6GB of ram on a 200x200 image and when I tried 200x300 it ate all 8GB of my RAM and brought everything to a crashing halt. Unless a majority of your image is white and you want to fill in tiny gaps or you can minimise where you apply the method I would avoid it. I would suggest writing you own class to examine each pixel that is not 255 and add the number of pixels surrounding it. You can then record the position of each pixel that was not 255 (in a simple list) and if your count was bellow a threshold set these positions to 255 in your images (by iterating though the list).
I would stick with the Aforge FillHoles class if you do not wish to write your own as it is designed for this purpose.
Cheers
Chris
you can use FillConvexPoly
image.FillConvexPoly(externalContours.ToArray(), new Gray(255));

Categories

Resources