I am developing an app where in users click images and upload them. There is a button in my app and on clicking it, the camera mode opens. User can click image and see preview. In preview the image looks good and it occupies whole screen. Later I have to display this image in a UIImageView of width 110 and height 111. When I display it in this, the image is getting distorted and cropped at edges. My main objective is to maintain aspect ratio.
I tried doing this.
- (void)displayCapturedImage
{
//[self.imageView setImage:self.capturedImage];
CGSize smallSize = CGSizeMake(110, 111);
[self.imageView setImage:[self imageWithImage:self.capturedImage scaledToSize:smallSize]];
}
- (UIImage*)imageWithImage:(UIImage*)image
scaledToSize:(CGSize)newSize;
{
UIGraphicsBeginImageContext( newSize );
[image drawInRect:CGRectMake(0,0,newSize.width,newSize.height)];
UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
but this didn't work for me. Image is still distorted.
You need to calculate the scale ratio yourself. Determine how much smaller the image needs to be and apply that to both the width and height.
Something like
float widthFactor = targetWidth / width;
float heightFactor = targetHeight / height;
if ( widthFactor < heightFactor )
scaleFactor = widthFactor;
else
scaleFactor = heightFactor;
scaledWidth = width * scaleFactor;
scaledHeight = height * scaleFactor;
if ( widthFactor < heightFactor )
thumbnailPoint.y = (targetHeight - scaledHeight) * 0.5;
else if ( widthFactor > heightFactor )
thumbnailPoint.x = (targetWidth - scaledWidth) * 0.5;
Related
I am trying to do this in storyboard but can't seem to figure it out. I have a QR code (square UIImageView) centered (vertically and horizontally) in another UIView that I want to expand a bit depending on phone size, but I don't want it to go over 150x150 otherwise it looks odd. Here is what I tried. Any help greatly appreciated.
You want to:
Center it in its superview
Keep 1:1 aspect ratio
Keep it less-than-or-equal-to 150x150
So, centering is obvious (yay, auto layout). 1:1 ratio is also easy. The Trick is:
Pin all 4 sides to the superview sides at >= 0.
Set the Width <= 150.
And then ---
Set another Width constraint, this time Width Equals: 150 but with Priority of less-than 1000. I used 999 and it did the job.
Pinning the sides and top/bottom with >= 0 gets it to shrink when the superview is smaller than 150 (in either direction).
I have been using this for a long time.
(UIImage*) scaleImage:(UIImage*)image toSize:(CGSize)newSize {
CGSize scaledSize = newSize;
float scaleFactor = 1.0;
if( image.size.width > image.size.height ) {
scaleFactor = image.size.width / image.size.height;
scaledSize.width = newSize.width;
scaledSize.height = newSize.height / scaleFactor;
}
else {
scaleFactor = image.size.height / image.size.width;
scaledSize.height = newSize.height;
scaledSize.width = newSize.width / scaleFactor;
}
UIGraphicsBeginImageContextWithOptions( scaledSize, NO, 0.0 );
CGRect scaledImageRect = CGRectMake( 0.0, 0.0, scaledSize.width,
scaledSize.height );
[image drawInRect:scaledImageRect];
UIImage* scaledImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return scaledImage;
}
Now that the Instagram app can handle and post non-square images from within that app, I was hoping that I could send non-square images to the Instagram app from my app using the same provided iPhone hooks I've been using (https://instagram.com/developer/iphone-hooks/?hl=en). However, it still seems to be cropping my images to square and not giving me the option to expand them to their non-square size (unlike when I load a non-square photo from the library from directly within the Instagram app and it lets me expand it to its non-square original dimensions). Anyone had any luck sending non-square images? I'm hoping there's some tweak that will make it work.
I too was hoping that they would take non-square photos with the update, but you're stuck with the old solution for posting non-square photos.... matte them to square with white.
https://github.com/ShareKit/ShareKit/blob/master/Classes/ShareKit/Sharers/Services/Instagram/SHKInstagram.m
- (UIImage *)imageByScalingImage:(UIImage*)image proportionallyToSize:(CGSize)targetSize {
UIImage *sourceImage = image;
UIImage *newImage = nil;
CGSize imageSize = sourceImage.size;
CGFloat width = imageSize.width;
CGFloat height = imageSize.height;
CGFloat targetWidth = targetSize.width;
CGFloat targetHeight = targetSize.height;
CGFloat scaleFactor = 0.0;
CGFloat scaledWidth = targetWidth;
CGFloat scaledHeight = targetHeight;
CGPoint thumbnailPoint = CGPointMake(0.0,0.0);
if (CGSizeEqualToSize(imageSize, targetSize) == NO) {
CGFloat widthFactor = targetWidth / width;
CGFloat heightFactor = targetHeight / height;
if (widthFactor < heightFactor)
scaleFactor = widthFactor;
else
scaleFactor = heightFactor;
scaledWidth = width * scaleFactor;
scaledHeight = height * scaleFactor;
// center the image
if (widthFactor < heightFactor) {
thumbnailPoint.y = (targetHeight - scaledHeight) * 0.5;
} else if (widthFactor > heightFactor) {
thumbnailPoint.x = (targetWidth - scaledWidth) * 0.5;
}
}
// this is actually the interesting part:
UIGraphicsBeginImageContext(targetSize);
[(UIColor*)SHKCONFIG(instagramLetterBoxColor) set];
CGContextFillRect(UIGraphicsGetCurrentContext(), CGRectMake(0,0,targetSize.width,targetSize.height));
CGRect thumbnailRect = CGRectZero;
thumbnailRect.origin = thumbnailPoint;
thumbnailRect.size.width = scaledWidth;
thumbnailRect.size.height = scaledHeight;
[sourceImage drawInRect:thumbnailRect];
newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
if(newImage == nil) NSLog(#"could not scale image");
return newImage ;
}
How do I resize UIImageView after setting its contentmode to UIViewContentModeScaleAspectFit so that i can remove the white spaces from top & below.
Please see the attached image
Thanks in advance:)
Hope this will help some others as well ,please do comment if it will break in some condition :)
- (CGRect)frameForImageattribute:(CGSize)image inImageViewAspectFit:(UIImageView *)imageView {
float imageRatio = image.width / image.height;
float viewRatio = imageView.frame.size.width / imageView.frame.size.height;
if (imageRatio < viewRatio) {
float scale = imageView.frame.size.height / image.height;
float width = scale * image.width;
return CGRectMake(kLeftPading, kTopPading, width, imageView.frame.size.height);
}
else {
float scale = imageView.frame.size.width / image.width;
float height = scale * image.height;
return CGRectMake(kLeftPading, kTopPading, imageView.frame.size.width, height);
}
}
You need to get the scale factor of the image view. it can be obtained by
float scaleFactor = MAX(image.size.width/imageView.bounds.size.width, image.size.height/imageView.bounds.size.height);
Then do
CGRect ivFrame = imageView.frame;
ivframe.size.height = image.size.height/scalefactor;
ivFrame.size.width = image.size.width/scalefactor;
imageView.frame = ivFrame;
theres probably a category out there that does this automagically.
EDIT: Heres one for the scalefactor calculation, and it even respects the content mode of the mageView:
how can I get the scale factor of a UIImageView who's mode is AspectFit?
If you know only the width of the imageview and when the height of the image is dynamic then you need to scale the image's height according to the given width to remove the white spaces above and below your image in UIViewContentModeScaleAspectFit mode. Use the following method from here to scale the height of the image according to the standard width of your screen.
-(UIImage*)imageWithImage: (UIImage*) sourceImage scaledToWidth: (float) i_width
{
float oldWidth = sourceImage.size.width;
float scaleFactor = i_width / oldWidth;
float newHeight = sourceImage.size.height * scaleFactor;
float newWidth = oldWidth * scaleFactor;
UIGraphicsBeginImageContext(CGSizeMake(newWidth, newHeight));
[sourceImage drawInRect:CGRectMake(0, 0, newWidth, newHeight)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
And call it from your cellForRowAtIndexPath: method like this:
UIImage *img = [dictImages objectForKey:yourImageKey]; // loaded the image
cell.imgView.image = [self imageWithImage:img scaledToWidth:self.view.frame.size.width];
I need to find the biggest centered square from a portrait or a landscape image scaled to a size.
E.g. if I get an image of size 1200x800 and I need to get the centered square down to size 300x300.
I found an answer on this question on stackoverflow which has been widely copied. However that answer is incorrect, so want to post the correct answer which is as follows:
+ (UIImage*) cropBiggestCenteredSquareImageFromImage:(UIImage*)image withSide:(CGFloat)side
{
// Get size of current image
CGSize size = [image size];
if( size.width == size.height && size.width == side){
return image;
}
CGSize newSize = CGSizeMake(side, side);
double ratio;
double delta;
CGPoint offset;
//make a new square size, that is the resized imaged width
CGSize sz = CGSizeMake(newSize.width, newSize.width);
//figure out if the picture is landscape or portrait, then
//calculate scale factor and offset
if (image.size.width > image.size.height) {
ratio = newSize.height / image.size.height;
delta = ratio*(image.size.width - image.size.height);
offset = CGPointMake(delta/2, 0);
} else {
ratio = newSize.width / image.size.width;
delta = ratio*(image.size.height - image.size.width);
offset = CGPointMake(0, delta/2);
}
//make the final clipping rect based on the calculated values
CGRect clipRect = CGRectMake(-offset.x, -offset.y,
(ratio * image.size.width),
(ratio * image.size.height));
//start a new context, with scale factor 0.0 so retina displays get
//high quality image
if ([[UIScreen mainScreen] respondsToSelector:#selector(scale)]) {
UIGraphicsBeginImageContextWithOptions(sz, YES, 0.0);
} else {
UIGraphicsBeginImageContext(sz);
}
UIRectClip(clipRect);
[image drawInRect:clipRect];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
Incorrect answer which I found earlier is as follows:
+ (UIImage*) cropBiggestCenteredSquareImageFromImage:(UIImage*)image withSide:(CGFloat)side
{
// Get size of current image
CGSize size = [image size];
if( size.width == size.height && size.width == side){
return image;
}
CGSize newSize = CGSizeMake(side, side);
double ratio;
double delta;
CGPoint offset;
//make a new square size, that is the resized imaged width
CGSize sz = CGSizeMake(newSize.width, newSize.width);
//figure out if the picture is landscape or portrait, then
//calculate scale factor and offset
if (image.size.width > image.size.height) {
ratio = newSize.width / image.size.width;
delta = (ratio*image.size.width - ratio*image.size.height);
offset = CGPointMake(delta/2, 0);
} else {
ratio = newSize.width / image.size.height;
delta = (ratio*image.size.height - ratio*image.size.width);
offset = CGPointMake(0, delta/2);
}
//make the final clipping rect based on the calculated values
CGRect clipRect = CGRectMake(-offset.x, -offset.y,
(ratio * image.size.width) + delta,
(ratio * image.size.height) + delta);
//start a new context, with scale factor 0.0 so retina displays get
//high quality image
if ([[UIScreen mainScreen] respondsToSelector:#selector(scale)]) {
UIGraphicsBeginImageContextWithOptions(sz, YES, 0.0);
} else {
UIGraphicsBeginImageContext(sz);
}
UIRectClip(clipRect);
[image drawInRect:clipRect];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
The problem with this code is that it does not crop correctly.
Both the codes can be tried on following image:
https://s3.amazonaws.com/anandprakash/ImageWithPixelGrid.jpg
Correct Algo generates following image on the above base url:
https://s3.amazonaws.com/anandprakash/ScreenshotCorrectAlgo.png
Wrong Algo generates following image on the above base url - notice the extra 50px on the width on each side.
https://s3.amazonaws.com/anandprakash/ScreenshotWrongAlgo.png
Same answer above as a Swift extension on UIImage:
private extension UIImage {
func cropBiggestCenteredSquareImage(withSide side: CGFloat) -> UIImage {
if self.size.height == side && self.size.width == side {
return self
}
let newSize = CGSizeMake(side, side)
let ratio: CGFloat
let delta: CGFloat
let offset: CGPoint
if self.size.width > self.size.height {
ratio = newSize.height / self.size.height
delta = ratio * (self.size.width - self.size.height)
offset = CGPointMake(delta / 2, 0)
}
else {
ratio = newSize.width / self.size.width
delta = ratio * (self.size.height - self.size.width)
offset = CGPointMake(0, delta / 2)
}
let clipRect = CGRectMake(-offset.x, -offset.y, ratio * self.size.width, ratio * self.size.height)
if UIScreen.mainScreen().respondsToSelector(#selector(NSDecimalNumberBehaviors.scale)) {
UIGraphicsBeginImageContextWithOptions(newSize, true, 0.0)
} else {
UIGraphicsBeginImageContext(newSize);
}
UIRectClip(clipRect)
self.drawInRect(clipRect)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage
}
}
I've an UIImageView with content mode Aspect Fit of size 220x155. I'm dynamically inserting different images in different resolutions, but all larger than the size of the UIImageView. As the content mode is set to Aspect Fit, the image is scaled with respect to the ratio to fit the UIImageView.
My problem is, that if for instance the image inside the UIImageView is scaled to 220x100, I would like the UIImageView to shrink from a height of 155 to 100 too to avoid space between my elements.
How can I do this?
If I got you right, it would be something like this: get image size by:
UIImage * img = [UIImage imageNamed:#"someImage.png"];
CGSize imgSize = img.size;
calculate scale ratio on width
float ratio=yourImageView.frame.size.width/imgSize.width;
check scaled height (using same ratio to keep aspect)
float scaledHeight=imgSize.height*ratio;
if(scaledHeight < yourImageView.frame.size.height)
{
//update height of your imageView frame with scaledHeight
}
Based on Michael's answer, here's a complete method
+ (CGSize)makeSize:(CGSize)originalSize fitInSize:(CGSize)boxSize
{
float widthScale = 0;
float heightScale = 0;
widthScale = boxSize.width/originalSize.width;
heightScale = boxSize.height/originalSize.height;
float scale = MIN(widthScale, heightScale);
CGSize newSize = CGSizeMake(originalSize.width * scale, originalSize.height * scale);
return newSize;
}
Edit for Steven Stefanik's answer: This method breaks when originalSize is {0, 0}. Maybe consider these changes
-(CGSize)makeSize:(CGSize)originalSize fitInSize:(CGSize)boxSize
{
if (originalSize.height == 0) {
originalSize.height = boxSize.height;
}
if (originalSize.width == 0) {
originalSize.width = boxSize.width;
}
float widthScale = 0;
float heightScale = 0;
widthScale = boxSize.width/originalSize.width;
heightScale = boxSize.height/originalSize.height;
float scale = MIN(widthScale, heightScale);
CGSize newSize = CGSizeMake(originalSize.width * scale, originalSize.height * scale);
return newSize;
}
Last edit:
It seems I misunderstood the question.
To get the actual size you could try:
CGSize imageSize = img.size;
CGSize viewSize = imageView.frame.size;
CGSize actualSize;
if (imageSize.width > imageSize.height) {
actualSize.width = imageSize.width > viewSize.width ? viewSize.width : imageSize.width;
actualSize.height = imageSize.height * actualSize.width / imageSize.width;
}
else {
actualSize.height = imageSize.height > viewSize.height ? viewSize.height : imageSize.height;
actualSize.width = imageSize.width * actualSize.height / imageSize.height;
}