Scale down NSImage results into pixel change? - objective-c

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!

Related

opengl texture mapping off by 5-8 pixels

I've got a bunch of thumbnails/icons packed right up next to each other in a texture map / sprite sheet. From a pixel to pixel relationship, these are being scaled up from being 145 pixels square to 238 screen pixels square. I was expecting to get +-1 or 2 pixel accuracy on the edges of the box when accessing the texture coordinates, so I'm also drawing a 4 pixel outline overtop of the thumbnail to hide this probable artifact. But I'm seeing huge variations in accuracy. Sometimes it's off in one direction, sometimes the other.
I've checked over the math and I can't figure out what's happening.
The the thumbnail is being scaled up about 1.64 times. So a single pixel off in the source texture coordinate could result in around 2 pixels off on the screen. The 4 pixel white frame over top is being drawn at a 1-1 pixel to fragment relationship and is supposed to cover about 2 pixels on either side of the edge of the box. That part is working. Here I've turned off the border to show how far off the texture coordinates are....
I can tweak the numbers manually to make it go away. But I have to shrink the texture coordinate width/height by several source pixels and in some cases add (or subtract) 5 or 6 pixels to the starting point. I really just want the math to work out or to figure out what I'm doing wrong here. This sort of stuff drives me nuts!
A bunch of crap to know.
The shader is doing the texture coordinate offsetting in the vertex shader...
v_fragmentTexCoord0 = vec2((a_vertexTexCoord0.x * u_texScale) + u_texOffset.s, (a_vertexTexCoord0.y * u_texScale) + u_texOffset.t);
gl_Position = u_modelViewProjectionMatrix * vec4(a_vertexPosition,1.0);
This object is a box which is a triangle strip with 2 tris.
Not that it should matter, but matrix applied to the model isn't doing any scaling. The box is to screen scale. The scaling is happening only in the texture coordinates that are being supplied.
The texture coordinates of the object as seen above are 0.00 - 0.07, then in the shader have an addition of an offset amount which is different per thumbnail. .07 out of 2048 is like 143. Originally I had it at .0708 which should be closer to 145 it was worse and showed more like 148 pixels from the texture. To get it to only show 145 source pixels I have to make it .0.06835 which is 140 pixels.
I've tried doing the math in a calculator and typing in the numbers directly. I've also tried doing like =1305/2048. These are going in to GLfloats not doubles.
This texture map image is PNG and is loaded with these settings:
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
but I've also tried GL_LINEAR with no apparent difference.
I'm not having any accuracy problems on other textures (in the same texture map) where I'm not doing the texture scaling.
It doesn't get farther off as the coords get higher. In the image above the NEG MAP thumb is right next to the HEAT MAP thumb and are off in different directions but correct at the seam.
here's the offset data for those two..
filterTypes[FT_gradientMap20].thumbTexOffsetS = 0.63720703125;
filterTypes[FT_gradientMap20].thumbTexOffsetT = 0.1416015625;
filterTypes[FT_gradientMap21].thumbTexOffsetS = 0.7080078125;
filterTypes[FT_gradientMap21].thumbTexOffsetT = 0.1416015625;
==== UPDATE ====
A couple of things off the bat I realized I was doing wrong and are discussed over here: OpenGL Texture Coordinates in Pixel Space
The width of a single thumbnail is 145. But that would be 0-144, with 145 starting the next one. I was using a width of 145 so that's going to be 1 pixel too big. Using the above center of pixel type math, we should actually go from the center of 0 to the center of 144. 144.5 - 0.5 = 144.
Using his formula of (2i + 1)/(2N) I made new offset amounts for each of the starting points and used the 144/2048 as the width. That made things better but still off in some areas. And again still off in one direction sometimes and the other other times. Although consistent for each x or y position.
Using a width of 143 proves better results. But I can fix them all by just adjusting the numbers manually to work. I want to have the math to make it work out right.
... or.. maybe it has something to do with min/mag filtering - although I read up on that and what I'm doing seems right for this case.
After a lot of experiments and having to create a grid-lined guide texture so I could see exactly how far off each texture was... I finally got it!
It's pretty simple actually.
uniform mat4 u_modelViewProjectionMatrix;
uniform mediump vec2 u_texOffset;
uniform mediump float u_texScale;
attribute vec3 a_vertexPosition;
attribute mediump vec2 a_vertexTexCoord0;
The precision of the texture coordinates. By specifying mediump it just fixed itself. I suspect this also would help solve the problem I was having in this question:
Why is a texture coordinate of 1.0 getting beyond the edge of the texture?
Once I did that, I had to go back to my original 145 width (which still seems wrong but oh well). And for what it's worth I ended up then going back to all my original math on all the texture coordinates. The "center of pixel" method was showing more of the neighboring pixels than the straight /2048 did.

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

Draw tiled images in CGContext with a scale transformation gives precision errors

I want to draw tiled images and then transform them by using the usual panning and zooming gestures. The problem that brings me here is that, whenever I have a scaling transformation of a large number of decimal places, a thin line of pixels (1 or 2) appears in the middle of the tiles. I managed to isolate the problem like this:
CGContextSaveGState(UIGraphicsGetCurrentContext());
CGContextSetFillColor(UIGraphicsGetCurrentContext(), CGColorGetComponents([UIColor redColor].CGColor));
CGContextFillRect(UIGraphicsGetCurrentContext(), rect);//rect from drawRect:
float scale = 0.7;
CGContextScaleCTM(UIGraphicsGetCurrentContext(), scale, scale);
CGContextDrawImage(UIGraphicsGetCurrentContext(), CGRectMake(50, 50, 100, 100), testImage);
CGContextDrawImage(UIGraphicsGetCurrentContext(), CGRectMake(150, 50, 100, 100), testImage);
CGContextRestoreGState(UIGraphicsGetCurrentContext());
With a 0.7 scale, the two images appear correctly tiled:
With a 0.777777 scale (changing line 6 to "float scale = 0.777777;"), the visual artifact appears:
Is there any way to avoid this problem? This happens with CGImage, CGLayer and primitive forms such as a rectangle. It also happens on MacOSx.
Thanks for the help!
edit: Added that this also happens with a primitive form, like CGContextFillRect
edit2: It also happens on MacOSx!
Quartz has a floating point coordinate system, so scaling may result in values that are not on pixel boundaries, resulting in visible antialiasing at the edges. If you don't want that, you have two options:
Adjust your scale factor so that all your scaled coordinates are integral. This may not always be possible, especially if you're drawing lots of things.
Disable anti-aliasing for your graphics context using CGContextSetShouldAntialias(UIGraphicsGetCurrentContext(), false);. This will result in crisp pixel boundaries, but anything but straight lines might not look very good.
When all is said and done, iOS is dealing with discrete pixels on integer boundaries. When your frames are reduced 0.7, the 50 is reduced to 35, right on a pixel boundary. At 0.777777 it is not - so iOS adapts and moves/shrinks/blends whatever.
You really have two choices. If you want to use scaling of the context, then round the desired value up or down so that it results in integral scaled frame values (your code shows 50 as the standard multiplication value.)
Otherwise, you can not scale the context, but scale the content one by one, and use CGIntegralRect to round all dimensions up or down as needed.
EDIT: If my suspicion is right, there is yet another option for you. Lets say you want a scale factor of .77777 and a frame of 50,50,100,100. You take the 50, multiply it by the scale, then round the return value up or down. Then you recompute the new frame by using that value divided by 0.7777 to get some fractional value, that when scaled by 0.7777 returns an integer. Quartz is really good at figuring out that you mean an integral value, so small rounding errors are ignored. I'd bet anything this will work just fine for you.

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).

CGRectGetWidth vs CGRect.size.width

Which is better to use? I prefer CGRect.size.width cause it looks nicer. But, my colleague says CGRectGetWidth is better.
CGRectGetWidth/Height will normalize the width or height before returning them. Normalization is basically just checking if the width or height is negative, and negating it to make it positive if so.
Answered here
A rect's width and height can be negative. I have no idea when this would be true in practice, but according to Apple docs:
CGGeometry Reference defines structures for geometric primitives and
functions that operate on them. The data structure CGPoint represents
a point in a two-dimensional coordinate system. The data structure
CGRect represents the location and dimensions of a rectangle. The data
structure CGSize represents the dimensions of width and height.
The height and width stored in a CGRect data structure can be
negative. For example, a rectangle with an origin of [0.0, 0.0] and a
size of [10.0,10.0] is exactly equivalent to a rectangle with an
origin of [10.0, 10.0] and a size of [-10.0,-10.0]. Your application
can standardize a rectangle—that is, ensure that the height and width
are stored as positive values—by calling the CGRectStandardize
function. All functions described in this reference that take CGRect
data structures as inputs implicitly standardize those rectangles
before calculating their results. For this reason, your applications
should avoid directly reading and writing the data stored in the
CGRect data structure. Instead, use the functions described here to
manipulate rectangles and to retrieve their characteristics.