Is it possible to scale an image inside a UIView without changing the UIView's bounds? (That is, while still clipping the image to the UIView's bounds, even as the image scales larger than the UIView.)
I found some code on a different SO post that scales the image in a UIView:
view.transform = CGAffineTransformScale(CGAffineTransformIdentity, _scale, _scale);
However, this seems to affect the view's bounds -- making them larger -- so that the UIView's drawing now stomps over other nearby UIViews as its contents grow larger. Can I make its contents scale larger, while keeping the clipping bounds the same?
The easiest way to scale image is to use UIImageView by setting its contentMode property.
If you have to use UIView to show the image, you may try to redraw the image in UIView.
1.subclass UIView
2.draw your image in drawRect
//the followed code draw the origin size of the image
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
[_yourImage drawAtPoint:CGPointMake(0,0)];
}
//if you want to draw as much as the size of the image, you should calculate the rect that the image draws into
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
[_yourImage drawInRect:_rectToDraw];
}
- (void)setYourImage:(UIImage *)yourImage
{
_yourImage = yourImage;
CGFloat imageWidth = yourImage.size.width;
CGFloat imageHeight = yourImage.size.height;
CGFloat scaleW = imageWidth / self.bounds.size.width;
CGFloat scaleH = imageHeight / self.bounds.size.height;
CGFloat max = scaleW > scaleH ? scaleW : scaleH;
_rectToDraw = CGRectMake(0, 0, imageWidth * max, imageHeight * max);
}
Related
I am going through 19th chapter of Big Nerd Ranch, iOS textbook and can not understand several parts of the function that takes in a big image and creates a thumbnail out of it. Have a look:
- (void)setThumbnailFromImage:(UIImage *)image
{
CGSize origImageSize = image.size;
// The rectangle of the thumbnail
CGRect newRect = CGRectMake(0, 0, 40, 40);
// Figure out a scaling ratio to make sure we maintain the same aspect ratio
float ratio = MAX(newRect.size.width / origImageSize.width,
newRect.size.height / origImageSize.height);
// Create a transparent bitmap context with a scaling factor
// equal to that of the screen
UIGraphicsBeginImageContextWithOptions(newRect.size, NO, 0.0);
// Create a path that is a rounded rectangle
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:newRect
cornerRadius:5.0];
// Make all subsequent drawing clip to this rounded rectangle
[path addClip];
// Center the image in the thumbnail rectangle
CGRect projectRect;
projectRect.size.width = ratio * origImageSize.width;
projectRect.size.height = ratio * origImageSize.height;
projectRect.origin.x = (newRect.size.width - projectRect.size.width) / 2.0;
projectRect.origin.y = (newRect.size.height - projectRect.size.height) / 2.0;
[image drawInRect:projectRect];
// Get the image from the image context; keep it as our thumbnail
UIImage *smallImage = UIGraphicsGetImageFromCurrentImageContext();
self.thumbnail = smallImage;
// Cleanup image context resources; we're done
UIGraphicsEndImageContext();
}
From my understanding we are getting the MAX of the two ratios and then we put a smaller edge of the original image equal to newRect's edge (which is 40 in our case), the other edge seemingly should stick out of the newRect since the edge would be larger than the edge of newRect, when we UIGraphicsGetImageFromCurrentImageContext(). That's my vague 'understanding'.
Could anyone please explain what is this whole code doing in a detailed way, especially, the centering part? If you know some tutorials that might be relevant, it would also be great.
I just took or added to the previous comments and tried to explain each part more clearly. You seemed to get the basic idea, so I hope this helps solidify everything.
- (void)setThumbnailFromImage:(UIImage *)image
{
CGSize origImageSize = image.size;
//Create new rectangle of your desired size
CGRect newRect = CGRectMake(0, 0, 40, 40);
//Divide both the width and the height by the width and height of the original image to get the proper ratio.
//Take whichever one is greater so that the converted image isn't distorted through incorrect scaling.
float ratio = MAX(newRect.size.width / origImageSize.width,
newRect.size.height / origImageSize.height);
// Create a transparent bitmap context with a scaling factor
// equal to that of the screen
// Basically everything within this builds the image
UIGraphicsBeginImageContextWithOptions(newRect.size, NO, 0.0);
// Create a path that is a rounded rectangle -- essentially a frame for the new image
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:newRect
cornerRadius:5.0];
// Applying path
[path addClip];
// Center the image in the thumbnail rectangle
CGRect projectRect;
// Scale the image with previously determined ratio
projectRect.size.width = ratio * origImageSize.width;
projectRect.size.height = ratio * origImageSize.height;
// I believe the anchor point of the new image is (0.5, 0.5), so here he is setting the position to be in the middle
// Half of the width and height added to whatever origin you have (in this case 0) will give the proper coordinates
projectRect.origin.x = (newRect.size.width - projectRect.size.width) / 2.0;
projectRect.origin.y = (newRect.size.height - projectRect.size.height) / 2.0;
// Add the scaled image
[image drawInRect:projectRect];
// Retrieving the image that has been created and saving it in memory
UIImage *smallImage = UIGraphicsGetImageFromCurrentImageContext();
self.thumbnail = smallImage;
// Cleanup image context resources; we're done
UIGraphicsEndImageContext();
}
I am using following code to make an image oval shape.
UIImage * fooImage = image;
CGRect imageRect = CGRectMake(0, 0, 291, 130);
UIGraphicsBeginImageContextWithOptions(imageRect.size,NO,0.0);
// create a bezier path defining rounded corners
UIBezierPath * path = [UIBezierPath bezierPathWithOvalInRect: CGRectMake(0.0, 0.0, CGRectGetWidth(img_blank.frame), CGRectGetHeight(img_blank.frame))];
// use this path for clipping in the implicit context
[path addClip];
// draw the image into the implicit context
[fooImage drawInRect:imageRect];
// save the clipped image from the implicit context into an image
UIImage *maskedImage = UIGraphicsGetImageFromCurrentImageContext();
img_blank.image=maskedImage;
Here image_blank is the UIImage which I am using, if image with greater width and less height comes then it will not strech. If I change the values, I won't get an oval shape that fits my UIImageview(img_blank).
This issue is that your rect size and your image size don't match.
When you:
[fooImage drawInRect:imageRect];
the image will be drawn skewed into that rectangle, which you've defined as CGRectMake(0, 0, 291, 130);
To get it to work, you need to create a second rectangle that expands the oval's rectangle to match the image'a width/height ratio. You can then use this second rectangle to draw the image so that the image will aspect-fill the oval.
This is some pseudo code that I've used in the past for similar problems:
// get the size of the image that we want to scale
CGSize imageSize = imageToDraw.size;
// get the size of the canvas we're trying to fill
CGSize canvasSize = imageRect.size;
// we need to see how the ratio of image width & height compare to the canvas
CGFloat horizontalRatio = canvasSize.width / imageSize.width;
CGFloat verticalRatio = canvasSize.height / imageSize.height;
// pick the ratio that requires the most scaling
CGFloat ratio = MAX(horizontalRatio, verticalRatio); //AspectFill
// calculate the size that we should draw the image so that it could fill
// the entire canvas with an aspect-fill rule
CGSize aspectFillSize = CGSizeMake(imageSize.width * ratio, imageSize.height * ratio);
// now draw the image, centering it and filling the canvas
[imageToDraw drawInRect:CGRectMake((canvasSize.width-aspectFillSize.width)/2,
(canvasSize.height-aspectFillSize.height)/2,
aspectFillSize.width,
aspectFillSize.height)];
I have a Drawing App of sorts, I would like to create a Snapshot of the Canvas UIView (both on and off screen) and then scale it down. The code I have for doing that take bloody for ever on an iPad 3. Simulator there is no delay. The Canvas is 2048x2048.
Is there another way I should be doing this? Or something I have a miss in the code?
Thank you!
-(UIImage *) createScreenShotThumbnailWithWidth:(CGFloat)width{
// Size of our View
CGSize size = editorContentView.bounds.size;
//First Grab our Screen Shot at Full Resolution
UIGraphicsBeginImageContext(size);
[editorContentView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *screenShot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//Calculate the scal ratio of the image with the width supplied.
CGFloat ratio = 0;
if (size.width > size.height) {
ratio = width / size.width;
} else {
ratio = width / size.height;
}
//Setup our rect to draw the Screen shot into
CGSize newSize = CGSizeMake(ratio * size.width, ratio * size.height);
//Send back our screen shot
return [self imageWithImage:screenShot scaledToSize:newSize];
}
Did you use the "Time Profiler" Instrument ("Product" Menu -> "Profile") to check where in your code you spend the most of your time? (use it with your Device of course, not the Simulator, to have realistic profiling). I'd guess it is not in the image capture portion you quoted in your question, but in your rescaling method imageWithImage:scaledToSize: method.
Instead of rendering the image at its whole size in a context, then rescaling the image to the final size, you should render the layer in the context directly at the expected size by applying some affine transform to the context.
So simply use CGContextConcatCTM(someScalingAffineTransform); on UIGraphicsGetCurrentContext() right after your line UIGraphicsBeginImageContext(size);, to apply an scaling affine transform that will make the layer be rendered at a different scale/size.
This way it will be directly rendered as the expected size which will be much faster, instead of being rendered at 100% and then having you to rescale it afterwards in a time-consuming way
Thank you AliSoftware, Here is the Code I ended up using:
-(UIImage *) createScreenShotThumbnailWithWidth:(CGFloat)width{
if (IoUIDebug & IoUIDebugSelectorNames) {
NSLog(#"%# - %#", INTERFACENAME, NSStringFromSelector(_cmd) );
}
// Size of our View
CGSize size = editorContentView.bounds.size;
//Calculate the scal ratio of the image with the width supplied.
CGFloat ratio = 0;
if (size.width > size.height) {
ratio = width / size.width;
} else {
ratio = width / size.height;
}
CGSize newSize = CGSizeMake(ratio * size.width, ratio * size.height);
//Create GraphicsContext with our new size
UIGraphicsBeginImageContext(newSize);
//Create Transform to scale down the Context
CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformScale(transform, ratio, ratio);
//Apply the Transform to the Context
CGContextConcatCTM(UIGraphicsGetCurrentContext(),transform);
//Render our Image into the the Scaled Graphic Context
[editorContentView.layer renderInContext:UIGraphicsGetCurrentContext()];
//Save a copy of the Image of the Graphic Context
UIImage* screenShot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return screenShot;
}
A CALayer can do it, and a UIImageView can do it. Can I directly display an image with aspect-fit with Core Graphics? The UIImage drawInRect does not allow me to set the resize mechanism.
If you're already linking AVFoundation, an aspect-fit function is provided in that framework:
CGRect AVMakeRectWithAspectRatioInsideRect(CGSize aspectRatio, CGRect boundingRect);
For instance, to scale an image to fit:
UIImage *image = …;
CRect targetBounds = self.layer.bounds;
// fit the image, preserving its aspect ratio, into our target bounds
CGRect imageRect = AVMakeRectWithAspectRatioInsideRect(image.size,
targetBounds);
// draw the image
CGContextDrawImage(context, imageRect, image.CGImage);
You need to do the math yourself. For example:
// desired maximum width/height of your image
UIImage *image = self.imageToDraw;
CGRect imageRect = CGRectMake(10, 10, 42, 42); // desired x/y coords, with maximum width/height
// calculate resize ratio, and apply to rect
CGFloat ratio = MIN(imageRect.size.width / image.size.width, imageRect.size.height / image.size.height);
imageRect.size.width = imageRect.size.width * ratio;
imageRect.size.height = imageRect.size.height * ratio;
// draw the image
CGContextDrawImage(context, imageRect, image.CGImage);
Alternatively, you can embed a UIImageView as a subview of your view, which gives you easy to use options for this. For similar ease of use but better performance, you can embed a layer containing the image in your view's layer. Either of these approaches would be worthy of a separate question, if you choose to go down that route.
Of course you can. It'll draw the image in whatever rect you pass. So just pass an aspect-fitted rect. Sure, you have to do a little bit of math yourself, but that's pretty easy.
here's the solution
CGSize imageSize = yourImage.size;
CGSize viewSize = CGSizeMake(450, 340); // size in which you want to draw
float hfactor = imageSize.width / viewSize.width;
float vfactor = imageSize.height / viewSize.height;
float factor = fmax(hfactor, vfactor);
// Divide the size by the greater of the vertical or horizontal shrinkage factor
float newWidth = imageSize.width / factor;
float newHeight = imageSize.height / factor;
CGRect newRect = CGRectMake(xOffset,yOffset, newWidth, newHeight);
[image drawInRect:newRect];
-- courtesy https://stackoverflow.com/a/1703210
I have a UIImage contained in a UIImageView. It's set to use the UIViewContentModeScaleAspectFit contentMode. The UIImageView is the size of the screen. The image is not, hence the scaleAspectFit mode. What I can't figure out is, where on the screen is the UIImage? What's it's frame? Where does the top left of the image appear on the screen? I can see it on the screen, but not in code. This should be simple, but I can't figure it out.
Try this in your UIImageView:
It will will compute the frame of the image, assuming you are using UIViewContentModeScaleAspectFit.
- (CGRect) imageFrame
{
float horizontalScale = self.frame.size.width / self.image.size.width;
float verticalScale = self.frame.size.height / self.image.size.height;
float scale = MIN(horizontalScale, verticalScale);
float xOffset = (self.frame.size.width - self.image.size.width * scale) / 2;
float yOffset = (self.frame.size.height - self.image.size.height * scale) / 2;
return CGRectMake(xOffset,
yOffset,
self.image.size.width * scale,
self.image.size.height * scale);
}
What it does is works out how much you need to shrink/enlarge the UIImage to fit it in the UIImageView in each dimension, and then picks the smallest scaling factor, to ensure that the image fits in the allotted space.
With that you know the size of the UI Image, and it's easy to calculate the X, Y offset w.r.t. the containing frame.