-
-
Save tomasbasham/10533743 to your computer and use it in GitHub Desktop.
@implementation UIImage (scale) | |
/** | |
* Scales an image to fit within a bounds with a size governed by | |
* the passed size. Also keeps the aspect ratio. | |
* | |
* Switch MIN to MAX for aspect fill instead of fit. | |
* | |
* @param newSize the size of the bounds the image must fit within. | |
* @return a new scaled image. | |
*/ | |
- (UIImage *)scaleImageToSize:(CGSize)newSize { | |
CGRect scaledImageRect = CGRectZero; | |
CGFloat aspectWidth = newSize.width / self.size.width; | |
CGFloat aspectHeight = newSize.height / self.size.height; | |
CGFloat aspectRatio = MIN ( aspectWidth, aspectHeight ); | |
scaledImageRect.size.width = self.size.width * aspectRatio; | |
scaledImageRect.size.height = self.size.height * aspectRatio; | |
scaledImageRect.origin.x = (newSize.width - scaledImageRect.size.width) / 2.0f; | |
scaledImageRect.origin.y = (newSize.height - scaledImageRect.size.height) / 2.0f; | |
UIGraphicsBeginImageContextWithOptions( newSize, NO, 0 ); | |
[self drawInRect:scaledImageRect]; | |
UIImage* scaledImage = UIGraphicsGetImageFromCurrentImageContext(); | |
UIGraphicsEndImageContext(); | |
return scaledImage; | |
} | |
@end |
In Rubymotion
class UIImage
# Scales an image to fit within a bounds with a size governed by
# the passed size. Also keeps the aspect ratio.
#
# newSize - the CGSize of the bounds the image must fit within.
# aspect - A Symbol stating the aspect mode (defaults: :min)
#
# Returns a new scaled UIImage
def scaleImageToSize(newSize, aspect = :fit)
scaledImageRect = CGRectZero
aspectRules = { :fill => :max } # else :min
aspectWidth = Rational(newSize.width, size.width)
aspectHeight = Rational(newSize.height, size.height)
aspectRatio = [aspectWidth, aspectHeight].send(aspectRules[aspect] || :min)
scaledImageRect.size = (size.width * aspectRatio).round
scaledImageRect.size.height = (size.height * aspectRatio).round
scaledImageRect.origin.x = Rational(newSize.width - scaledImageRect.size.width, 2.0).round
scaledImageRect.origin.y = Rational(newSize.height - scaledImageRect.size.height, 2.0).round
UIGraphicsBeginImageContextWithOptions(newSize, false, 0)
drawInRect(scaledImageRect)
scaledImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
scaledImage
end
end
In Swift 2 (with keeping aspect ratio)
func imageWithSize(size:CGSize) -> UIImage
{
var scaledImageRect = CGRect.zero;
let aspectWidth:CGFloat = size.width / self.size.width;
let aspectHeight:CGFloat = size.height / self.size.height;
let aspectRatio:CGFloat = min(aspectWidth, aspectHeight);
scaledImageRect.size.width = self.size.width * aspectRatio;
scaledImageRect.size.height = self.size.height * aspectRatio;
scaledImageRect.origin.x = (size.width - scaledImageRect.size.width) / 2.0;
scaledImageRect.origin.y = (size.height - scaledImageRect.size.height) / 2.0;
UIGraphicsBeginImageContextWithOptions(size, false, 0);
self.drawInRect(scaledImageRect);
let scaledImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return scaledImage;
}
If you want "aspect fill", instead of "aspect fit", change function min
to max
.
Good job!
great job reinderdevries
Thank you reinderdevries!!!
Thanks for the code everyone! Here is a Swift 3 version.
// MARK: - Image Scaling.
extension UIImage {
/// Scales an image to fit within a bounds with a size governed by the passed size. Also keeps the aspect ratio.
/// Switch MIN to MAX for aspect fill instead of fit.
///
/// - parameter newSize: newSize the size of the bounds the image must fit within.
///
/// - returns: a new scaled image.
func scaleImageToSize(newSize: CGSize) -> UIImage {
var scaledImageRect = CGRect.zero
let aspectWidth = newSize.width/size.width
let aspectheight = newSize.height/size.height
let aspectRatio = max(aspectWidth, aspectheight)
scaledImageRect.size.width = size.width * aspectRatio;
scaledImageRect.size.height = size.height * aspectRatio;
scaledImageRect.origin.x = (newSize.width - scaledImageRect.size.width) / 2.0;
scaledImageRect.origin.y = (newSize.height - scaledImageRect.size.height) / 2.0;
UIGraphicsBeginImageContext(newSize)
draw(in: scaledImageRect)
let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return scaledImage!
}
}
This method returns a blurry version of the original image for me even though it's scaled down.
Modification of @akshaynhegde for a more generic implementation using an enum for scaling mode, and a pretty interface:
The final interface is as simple as this:
image.scaled(to: size)
image.scaled(to: size, scalingMode: .aspectFill)
image.scaled(to: size, scalingMode: .aspectFit)
.. and is implemented like this:
// MARK: - Image Scaling.
extension UIImage {
/// Represents a scaling mode
enum ScalingMode {
case aspectFill
case aspectFit
/// Calculates the aspect ratio between two sizes
///
/// - parameters:
/// - size: the first size used to calculate the ratio
/// - otherSize: the second size used to calculate the ratio
///
/// - return: the aspect ratio between the two sizes
func aspectRatio(between size: CGSize, and otherSize: CGSize) -> CGFloat {
let aspectWidth = size.width/otherSize.width
let aspectHeight = size.height/otherSize.height
switch self {
case .aspectFill:
return max(aspectWidth, aspectHeight)
case .aspectFit:
return min(aspectWidth, aspectHeight)
}
}
}
/// Scales an image to fit within a bounds with a size governed by the passed size. Also keeps the aspect ratio.
///
/// - parameter:
/// - newSize: the size of the bounds the image must fit within.
/// - scalingMode: the desired scaling mode
///
/// - returns: a new scaled image.
func scaled(to newSize: CGSize, scalingMode: UIImage.ScalingMode = .aspectFill) -> UIImage {
let aspectRatio = scalingMode.aspectRatio(between: newSize, and: size)
/* Build the rectangle representing the area to be drawn */
var scaledImageRect = CGRect.zero
scaledImageRect.size.width = size.width * aspectRatio
scaledImageRect.size.height = size.height * aspectRatio
scaledImageRect.origin.x = (newSize.width - size.width * aspectRatio) / 2.0
scaledImageRect.origin.y = (newSize.height - size.height * aspectRatio) / 2.0
/* Draw and retrieve the scaled image */
UIGraphicsBeginImageContext(newSize)
draw(in: scaledImageRect)
let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return scaledImage!
}
}
This could be improved to support more scaling modes such as .fit
, but the topic says keeping the aspect ratio.
In Xamarin.iOS
public static class UIImageHelper
{
public static UIImage ScaledImage(this UIImage self, CGSize newSize)
{
CGRect scaledImageRect = CGRect.Empty;
double aspectWidth = newSize.Width / self.Size.Width;
double aspectHeight = newSize.Height / self.Size.Height;
double aspectRatio = Math.Min(aspectWidth, aspectHeight);
scaledImageRect.Size = new CGSize(self.Size.Width * aspectRatio, self.Size.Height * aspectRatio);
scaledImageRect.X = (newSize.Width - scaledImageRect.Size.Width) / 2.0f;
scaledImageRect.Y = (newSize.Height - scaledImageRect.Size.Height) / 2.0f;
UIGraphics.BeginImageContextWithOptions(newSize, false, 0);
self.Draw(scaledImageRect);
UIImage scaledImage = UIGraphics.GetImageFromCurrentImageContext();
UIGraphics.EndImageContext();
return scaledImage;
}
}
@Oyvindkg, if you call this method with .aspectFit it produces an image with the specified 'newSize' size (let's say 800x800). What you would expect to get is an image scaled according to its aspect ratio with max 800 width or height (depending on the image size and form factor). In this example, the result is a squared, resized image of (e.g.) a landscape picture.
This can be fixed by setting the origin coordinates to 0:
scaledImageRect.origin.x = 0
scaledImageRect.origin.y = 0
and by using the computed, scaled rect as input for the UIGraphicsContext to begin:
UIGraphicsBeginImageContext(scaledImageRect.size)
And if the image is in a URL type http://domain.com/myimage.jpg
obtained with JSON, as I do to resize it to show it in a UIImage so that it keeps the ratio?
Thank you for your work, guys!
@akshaynhegde, @Oyvindkg, the method returns a blurry version of the original image, it is scaled, but blurry. Any idea how to fix it?
Swift 4
extension UIImage {
func scale(with size: CGSize) -> UIImage? {
var scaledImageRect = CGRect.zero
let aspectWidth:CGFloat = size.width / self.size.width
let aspectHeight:CGFloat = size.height / self.size.height
let aspectRatio:CGFloat = min(aspectWidth, aspectHeight)
scaledImageRect.size.width = self.size.width * aspectRatio
scaledImageRect.size.height = self.size.height * aspectRatio
scaledImageRect.origin.x = (size.width - scaledImageRect.size.width) / 2.0
scaledImageRect.origin.y = (size.height - scaledImageRect.size.height) / 2.0
UIGraphicsBeginImageContextWithOptions(size, false, 0)
self.draw(in: scaledImageRect)
let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return scaledImage
}
}
@shinrikiken - your code is not giving me the perfect image which should fits to the image size.
@lee-ho, perhaps you can replace this line
UIGraphicsBeginImageContext(newSize)
with this
UIGraphicsBeginImageContextWithOptions(newSize, false, UIScreen.main.scale)
and try?
Does anyone have the version of this to work in NSImage (for MacOS)?
Swift 5 (Mac Os)
This working for me but I'm sure if is the best approach
extension NSImage {
/// Represents a scaling mode
enum ScalingMode {
case aspectFill
case aspectFit
/// Calculates the aspect ratio between two sizes
///
/// - parameters:
/// - size: the first size used to calculate the ratio
/// - otherSize: the second size used to calculate the ratio
///
/// - return: the aspect ratio between the two sizes
func aspectRatio(between size: CGSize, and otherSize: CGSize) -> CGFloat {
let aspectWidth = size.width/otherSize.width
let aspectHeight = size.height/otherSize.height
switch self {
case .aspectFill:
return max(aspectWidth, aspectHeight)
case .aspectFit:
return min(aspectWidth, aspectHeight)
}
}
}
/// Scales an image to fit within a bounds with a size governed by the passed size. Also keeps the aspect ratio.
///
/// - parameter:
/// - newSize: the size of the bounds the image must fit within.
/// - scalingMode: the desired scaling mode
///
/// - returns: a new scaled image.
func scaled(to newSize: CGSize, scalingMode: ScalingMode = .aspectFill) -> NSImage {
let aspectRatio = scalingMode.aspectRatio(between: newSize, and: size)
/* Build the rectangle representing the area to be drawn */
var scaledImageRect = CGRect.zero
scaledImageRect.size.width = size.width * aspectRatio
scaledImageRect.size.height = size.height * aspectRatio
scaledImageRect.origin.x = (newSize.width - size.width * aspectRatio) / 2.0
scaledImageRect.origin.y = (newSize.height - size.height * aspectRatio) / 2.0
let scaledImage = NSImage(size: newSize)
scaledImage.lockFocus()
draw(in: scaledImageRect)
scaledImage.unlockFocus()
return scaledImage
}
}
In Swift: