3

I'm having an image variable which contains a .png picture.

In order to calculate how big it would be on disk I'm currently asving it to a memory cache and then using the length of that "memory file" to calculate if it is within the file sizes I want.

As that seems pretty inefficient to me (a "real" calculation is probably faster and less memory intense) I'm wondering if there is a way to do it in a different way.

Example:

private bool IsImageTooLarge(Image img, long maxSize)
{
    using (MemoryStream ms = new MemoryStream())
    {
        img.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
        if (ms.ToArray().Length > maxSize)
        {
             return true;
        }
    }
    return false;
}

Additional infos: The source code is part of what will be a .dll thus web specific things won't work there as I need to do things with C# itself.

Thomas
  • 2,886
  • 3
  • 34
  • 78
  • How accurate do you need it to be? – H H Dec 01 '14 at 10:30
  • I need to be able to say that it is larger than 100kb or less. so pretty accurate sadly (reason behind that is that I can only save images with a maximum of 100kb to disk) – Thomas Dec 01 '14 at 10:31
  • Use Jquery? http://stackoverflow.com/questions/1601455/how-to-check-file-input-size-with-jquery – Paul Zahra Dec 01 '14 at 10:32
  • jquery won't work tehre as its for a dll (will add that info to the question) – Thomas Dec 01 '14 at 10:34
  • If it's in the 100kB range then just use the MemoryStream. Relatively slow but in absolute terms very small and fast. – H H Dec 01 '14 at 10:35
  • :/ sadly there are some problems with that method though. If you use code covering utilities it can be that you need to use GC.Collect() + GC.WaitForPendingFinalizers() in almost every image operation method else you run into troubles as the memory is not freed fast enough. The above method is there one of the most memory intense ones so I chose to start with that one to see if there is any alternative there that is less memory intense and possibly even faster. – Thomas Dec 01 '14 at 10:40
  • That `.ToArray()` is the main culprit. Remove it. – H H Dec 01 '14 at 10:56
  • You don't have code-covering in production, do you? That remark makes little sense to me. And just being mem intensive is not a problem per se. – H H Dec 01 '14 at 11:01
  • @HenkHolterman no that is for the developement the code coverage. And the mem intensive wouldn't be a problem if I hadn't to expect 5000 pics and only had 5 so far and already running into an out of memory exception. – Thomas Dec 01 '14 at 11:05
  • When 5 smallish pictures cause a problem then the causes are not what you think they are. – H H Dec 01 '14 at 17:41
  • Sadly they are as I could trace it to the image functions. Because when I run GC.Collect AND gc.WaitForPendingFinalizers inside the image functions the memory usage goes down from >500MB (which causes the out of memory exception) to less than 50MB. The problem is mostly how often those 5 pics are processed (normally I wouldnt have expected that to be any prob but like I said I could trace it down to the image functions so changing them one by one, and this one is one I wasnt sure how it could be changed) – Thomas Dec 01 '14 at 20:22

2 Answers2

2

You can save on memory by implementing your own Stream, say, PositionNullStream, which would be similar to NullStream class behind the Stream.Null object, but with the position counter. Your implementation would provide a write-only stream to the Save method of the image, and collect the current position from it when the Save has finished.

private bool IsImageTooLarge(Image img, long maxSize)
{
    using (var ps = new PositionNullStream()) {
        img.Save(ps, System.Drawing.Imaging.ImageFormat.Png);
        return ps.Position > maxSize;
    }
}

You can find a sample implementation of NullStream on lines 1445..1454 here. Change the implementation to store the current position when writing and re-positioning methods are called (NullStream is hardcoded to return zero).

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • So the idea would be to create a class called PositionNullStream that is derived from stream and similar to NullStream in essence (except position)? – Thomas Dec 01 '14 at 10:50
  • 1
    @Thomas That's right. By the way, your call to `ToArray` is redundant - you can get the same info by checking `ms.Position`. – Sergey Kalinichenko Dec 01 '14 at 10:53
  • But remember that this does all the work and throws it away. While it may use less memory than your solution (to be proven), it will certainly not be faster. – DasKrümelmonster Dec 01 '14 at 10:53
  • @DasKrümelmonster Yes but I'm already happy if "just" the memory footprint is less. Faster would be nice but is not 100% necessary. Going to try that method after lunchbreak – Thomas Dec 01 '14 at 10:57
0

No, there is no way. Because you do not need the size of the picture but the size of the file. The only way to get it - with anything involving compression - is to compress it and see what comes out.

Without that you can say X * Y * Bytes Per Pixel - but that is the bitmap, not anything with compression.

TomTom
  • 61,059
  • 10
  • 88
  • 148
  • You could use an estimate for the compression. Depends on the required accuracy. – H H Dec 01 '14 at 10:54
  • That is extremely estimate. Some things are tremendously well in compression, others are not. – TomTom Dec 01 '14 at 10:56
  • 1
    But this measure might be useful as a ceiling, and you might find that compressed images have a fairly common fraction of that that allows you to eliminate most of those that don't come in under threshold. You could do a two pass filter, and only have to go to MemoryStream or similar for images where say bitmap size * .5 > 100KB ... ? – Tim Barrass Dec 01 '14 at 10:57
  • But beware optimizing prematurely :) – Tim Barrass Dec 01 '14 at 10:58
  • If an estimation is accurate enough to be useable for an upper ceiling then that would also improve the speed and memory footprint of the method as the method would then only do its stream job when the ceiling is not reached. – Thomas Dec 01 '14 at 10:59
  • You can actually do 3 bands. One - the uncompressed image is smaller than allowed = allow. Two, the uncompressed image is way too large anyway (risky but doable), and 3 - try and find out. BUT: why do this at all? The image has to come from somewhere - likely that has a good size (outside of being a full image editor in most cases you would just use the size of the input file). – TomTom Dec 01 '14 at 11:06
  • The problem in this case is: I'm resizing the image in that instance and need to find out if the resized image (I'm rescaling it downwards so that it fits) fits the 100k limit. If not: Rescale again to a lower size. (but as it has nothing to do with the problem of the image being in the memory for at least some time and it not being as fast as I would like I didnt mention it in the question as for that part I saw it as not necessary for mentioning) – Thomas Dec 01 '14 at 12:28
  • If you are resizing a compressed image within reason you'll most likely get a similar compression as the compression is influenced most by the content and its granularity. – TaW Dec 01 '14 at 12:54