Algorithm or math to project a GIF file size? - objective-c

I have a user's animated gif file that is about 10mb. I'd like to allow users to upload it and let me host it on my server, but I'd like to rescale it to fit a maximum file size of 5mb to conserve bandwidth from hotlinking.
I have a basic method right now that determines a targetWidth and targetHeight based on pixel surface area.
It works well enough:
CGFloat aspectRatio = originalHeight / originalWidth;
CGFloat reductionFactor = desiredFileSize / originalFileSize;
CGFloat targetSurfaceArea = originalSurfaceArea * reductionFactor;
int targetHeight = targetSurfaceArea / sqrt(targetSurfaceArea/aspectRatio);
int targetWidth = targetSurfaceArea / targetHeight;
Its fairly accurate, ex. results: a 27mb file will turn into 3.3mb, or a 13.9mb will turn into 5.5mb.
I would like to tune this accuracy to get much closer to 5mb, and I was hoping someone would know a bit more about how gif color / frame count could better be factored into this algorithm. Thanks

Not sure you're going to find an easy way to do this. Projecting the compressed size of a file without running the compression algorithm seems to me to be non deterministic.
However, if you have plenty of compute cycles you could use an approximation based approach. Use the algorithm above to give you a first resize of the image. If the resulting file is > than 5Mb, half the resize percentage and try again. If < 5Mb add 50% to the resize percentage and try again. Repeat until you get sufficiently close to 5Mb.
So, for example
50% = 3.3Mb, so try halfway between 50 and 100
75% = 6.1Mb, so try halfway between 75 and 50
62.5% = 4.7Mb so try halfway between 62.5 and 75
etc

Related

What is the right way to resize using NVIDIA NPP to exact destination dimensions?

I'm trying to use NVIDIA NPP to experiment with some image resizing routines. I want to resize to an exact dimension. I've been looking at image resizing using NVIDIA NPP but all of its resize functions take scale factors for X and Y Dimensions, and I could not see any API taking direct destination dimensions.
As an example, this is one API:
NppStatus nppiResizeSqrPixel_8u_C1R(const Npp8u * pSrc, NppiSize oSrcSize, int nSrcStep, NppiRect oSrcROI, Npp8u * pDst, int nDstStep, NppiRect oDstROI, double nXFactor, double nYFactor, double nXShift, double nYShift, int eInterpolation);
I realize one way could be to find the appropriate scale factor the destination dimension, but we don't exactly know how the API decides destination ROI based on scale factor (since it is floating point math). We could reverse the calculation in the jpegNPP sample to find the scale factor, but the API itself does not make any guarantees so I'm not sure how safe it is. Any ideas what are the possibilities?
As a side question, the API also takes two params, nXShift and nYShift, but just says "Source pixel shift in x-direction". I'm not exactly clear what shift is being talked about here. Do you have an idea?
If I wanted to map the whole SRC image to the smaller rectangle in the DST image as shown in the image below I would use xFactor = yFactor = 0.5 and xShift = 0.5*DST.width and yShift = 0.
Mapping src to half size destination image
In other words, the pixel at (x,y) in the SRC is mapped to the pixel (x',y') in the DST as
x' = xFactor * x + xShift
y' = yFactor * y + yShift
In this case, both the source and dest ROI could be the entire support of the respective images.

Scale down NSImage results into pixel change?

I'm using the following code to scale down my image:
NSImage * smallImage = [[NSImage alloc] initWithSize:CGSizeMake(width, height)];
[smallImage lockFocus];
[[NSGraphicsContext currentContext]
setImageInterpolation:NSImageInterpolationHigh];
[image drawInRect:CGRectMake(0, 0, width, height)
fromRect:NSZeroRect
operation:NSCompositeCopy
fraction:1.0];
[smallImage unlockFocus];
Basically, this works fine, but if I set the width and height to exactly as the original one, and compare the images pixel by pixel, there are still some pixels changed.
And since my app is pixel-sensitive, I need to make sure every pixel is correct, so I'm wondering how can I keep pixels as they are during such scale down, is it possible?
Yes, NSImage will change the image data in various ways. It attempts to optimize the "payload" image data according to the size needed for its graphical representation on the UI.
Scaling it down and up again is generally not a good idea.
AFAIK you can only avoid that by keeping the original image data somehere else (e.g. on disk or in a separate NSData container or so).
If you need to apply calcluations or manipulations on the image data which needs to be 100% accurate down to each pixel, then work with NSData or C strings/byte arrays only. Avoid NSImage unless
a) the result is for presentations on the device only
b) you really need functionality that comes with NSImage objects.
I am explaining the problems in principle, not scientific.
Pixels have a fixed size, for technical reasons.
No, you can't keep your pixels, when scaling down.
An example to explain: Pixelsize in square 0,25 inch. Now you want to fill a square wich 1,1 inch. It's impossible. How many pixels should be used? 4 = too less, 5 too much. Now in the COCOA libs or wherever it happens, a decision is made: better more pixels = enlarging square size, or less = reducing square size. That's out of control for you.
Another problem is - also out of control for you - the way how measures are computed.
An example: 1 inch is nearly 2.54 cm, so 1.27 is 0.5 inch, but what is 1.25 cm? Values, not only measures are internally computed using one measure-unit: I think it's inch (as DOUBLE, with fixed number of digits after the period). When using the unit cm it is internally recomputed in inch, some mathematical operations are done (e.g. How many pixels are neccessary for the square?) and the result is sent back, maybe recomputed in cm. That also happens when using INTEGER, internally computed as DOUBLE and returned as INTEGERS. Funny things = unexpected values happen from that, especially after divisions, which are used for scaling down!
By the way: If an image is scaled, often new pixels are created for the scaled image. For example, if you have 4 pixels: 2 red, 2 blue, the new ONE has a mixed color, somehow violet. There is no way back. So always work on copies of an image!

Resize image and keep aspect ratio

I'm trying to make zoom in/out buttons, but for whatever reason I just can't figure out how to maintain the aspect ratio and resize the image by - say 90% or 110%
The issue is that I'm trying to make it so that when you click the zoom out button 4 times, then click the zoom in button 4 times, the image would be its original size. There's no defined width since I'm trying to make the new width be 90%/110% of the existing width, but obviously multiplying by 0.9 and 1.1 doesn't do that correctly.
I currently have the following code..
Dim source As New Bitmap(PictureBox1.Image)
Dim NewWidth As Integer = source.Width * 0.9
Dim NewHeight As Integer = NewWidth * (source.Height / source.Width)
Any help is appreciated. I'm sure I'm just over-thinking it again, but some guidance would be appreciated :)
The best approach is to begin each resize operation with a copy of the original image. Have your buttons represent the total zoom factor (so say add 0.1 zoom for the + and subtract 0.1 zoom for the -).
You want to start with the original image each time because otherwise successive operations will quickly distort the image due to the interpolation inherent in zooming in and out.

How to quickly estimate file sizes of resized images on iOS?

In Mail, when I add an image and try to send it, it quickly asks me which size I want to send the images as. See screenshot:
I want to do something similar in an app where I will be uploading an image and want to enable the user to resize the image before it is uploaded. What is the best way to estimate the file size as Apple does here?
It seems that it would take too long to actually create each of the resized images only to check them for sizes. Is there a better way?
I did find this Apple sample code which helps a little bit but to be honest is a bit overwhelming. :)
The single biggest factor in determining the final compressed image size is not image size or JPEG compression quality, but image complexity (lit. entropy). If you know that you're always going to be dealing with highly-detailed photos (as opposed to solid color fields or gradients), that somewhat reduces the variance along that dimension, but...
I spent a fair amount of time doing numerical analysis on this problem. I sampled the compressed image size of a detailed, high-resolution image that was scaled down in 10 percentage point increments, at 9 different JPEG quality levels. This produced a 3-dimensional data set describing an implicit function z = (x, y) where x is the scaled image size in pixels (w*h), y is the JPEG compression quality, and z is the size of the resulting image in bytes.
The resulting surface is hard to estimate. Counterintuitively, it has oscillations and multiple inflection points, meaning that a function of degree 2 in both x and y is insufficient to fit it, and increasing the polynomial degrees and creating custom fitting functions didn't yield significantly better results. Not only is it not a linear relation, it isn't even a monotonic relation. It's just complex.
Let's get practical. Notice when Apple prompts you for the image size: when you hit "Send", not when the image first appears in the mail composition view. This gives them as long as it takes to compose your message before they have to have the estimated image sizes ready. So my suspicion is this: they do it the hard way. Scaling the image to the different sizes can be parallelized and performed in the background, and even though it takes several seconds on iPhone 4-calibur hardware, all of that work can be hidden from the user. If you're concerned about memory usage, you can write the images to temporary files and render them sequentially instead of in parallel, which will use no more than ~2x the memory of the uncompressed file in memory.
In summary: unless you know a lot about the expected entropy of the images you're compressing, any estimation function will be wildly inaccurate for some class of images. If you can handle that, then it's fairly easy to do a linear or quadratic fit on some sample data and produce a function for estimation purposes. However, if you want to get as close as Apple does, you probably need to do the actual resizing work in the background, since there are simply too many factors to construct a heuristic that gets it right all of the time.
I have built a method that would resize the image, like so:
-(UIImage *)resizeImage:(UIImage *)image width:(CGFloat)resizedWidth height:(CGFloat)resizedHeight
{
CGImageRef imageRef = [image CGImage];
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef bitmap = CGBitmapContextCreate(NULL, resizedWidth, resizedHeight, 8, 4 * resizedWidth, colorSpace, kCGImageAlphaPremultipliedFirst);
CGContextDrawImage(bitmap, CGRectMake(0, 0, resizedWidth, resizedHeight), imageRef);
CGImageRef ref = CGBitmapContextCreateImage(bitmap);
UIImage *result = [UIImage imageWithCGImage:ref];
CGContextRelease(bitmap);
CGImageRelease(ref);
return result;
}
And to get the size of the image, you would have to convert it into NSData, and ask for the length:
UIImage* actualImage = [UIImage imageNamed:#"image"];
NSData* actualImageData = UIImagePNGRepresentation(actualImage);
NSLog(#"Actual %f KB", (CGFloat)actualImageData.length / (CGFloat)1024);
UIImage* largeImage = [self resizeImage:actualImage width:actualImage.size.width * 0.8 height:actualImage.size.height * 0.8];
NSData* largeImageData = UIImagePNGRepresentation(largeImage);
NSLog(#"Large %f KB", (CGFloat)largeImageData.length / (CGFloat)1024);
UIImage* mediumImage = [self resizeImage:actualImage width:actualImage.size.width * 0.5 height:actualImage.size.height * 0.5];
NSData* mediumImageData = UIImagePNGRepresentation(mediumImage);
NSLog(#"Medium %f KB", (CGFloat)mediumImageData.length / (CGFloat)1024);
UIImage* smallImage = [self resizeImage:actualImage width:actualImage.size.width * 0.3 height:actualImage.size.height * 0.3];
NSData* smallImageData = UIImagePNGRepresentation(smallImage);
NSLog(#"Small %f KB", (CGFloat)smallImageData.length / (CGFloat)1024);
You can always use the UIImageJPEGRepresentation to compress an image. The four options can be values ranging 0.25, 0.5, 0.75 and 1.0 whose size can be found out easily by calculations on image after applying the same method.
The image sizes provided in the Mail app are only estimates - the actual filesize of the sent image is different. It would be also be far too slow to convert a full-size image (3264 x 2448 in the iPhone 4S) to the various sizes, just to get the filesize.
[edit]
The compression filesizes aren't linear, so you can't just get numPixels/filesize to accurately estimate the filesize for smaller images.
So this answer isn't totally useless, here are the image sizes the Mail.app exports at:
Small: 320x240
Medium: 640x480
Large: 1224x1632
If you store it to NSData you can call [NSData length] to get number of bytes contained and then divide it to get proper sizes in kB or MB

Comparing two images - Detect egg in a nest

I have a webcam directly over a chicken nest. This camera takes images and uploads them to a folder on a server. I'd like to detect if an egg has been laid from this image.
I'm thinking the best method would be to compare the contrast as the egg will be much more reflective than the straw nest. (The camera has Infrared so the image is partly grey scale)
I'd like to do this in .NET if possible.
Try to resize your image to a smaller size, maybe 10 x 10 pixel. This averages out any small disturbing details.
Const N As Integer = 10
Dim newImage As New Bitmap(N, N)
Dim fromCamera As Image = Nothing ' Get image from camera here
Using gr As Graphics = Graphics.FromImage(newImage)
gr.SmoothingMode = SmoothingMode.HighSpeed
gr.InterpolationMode = InterpolationMode.Bilinear
gr.PixelOffsetMode = PixelOffsetMode.HighSpeed
gr.DrawImage(fromCamera, New Rectangle(0, 0, N, N))
End Using
Note: you do not need a high quality, but you need a good averaging. Maybe you will have to test different quality settings.
Since now, a pixel covers a large area of your original image, a bright pixel is very likely part of an egg. It might also be a good idea to compare the brightness of the brightest pixel to the average image brightness, since that would reduce problems due to global illumination changes.
EDIT (in response to comment):
Your code is well structured and makes sense. Here some thoughts:
Calculate the gray value from the color value with:
Dim grayValue = c.R * 0.3 + c.G * 0.59 + c.B * 0.11
... instead of comparing the three color components separately. The different weights are due to the fact, that we perceive green stronger than red and red stronger than blue. Again, we do not want a beautiful thumbnail we want a good contrast. Therefore, you might want to do some experiments here as well. May be it is sufficient to use only the red component. Dependent on lighting conditions one color component might yield a better contrast than others. I would recommend, to make the gray conversion part of the thumbnail creation and to write the thumbnails to a file or to the screen. This would allow you to play with the different settings (size of the thumbnail, resizing parameters, color to gray conversion, etc.) and to compare the (intermediate) results visually. Creating a bitmap (bmp) with the (end-)result is a very good idea.
The Using statement does the Dispose() for you. It does it even if an exception should occur before End Using (There is a hidden Try Finally involved).