My app crash when i upload an image to parse because of the fill size how do i shrink it? [duplicate] - xcode
I've been searching google, and have only come across libraries that either reduce the height/width or some how edit the UIImage appearance via CoreImage. But I have not seen or found one library, post that explains how to reduce image size so when it uploads, it's not the full image size.
so far I have this:
if image != nil {
//let data = NSData(data: UIImagePNGRepresentation(image))
let data = UIImagePNGRepresentation(image)
body.appendString("--\(boundary)\r\n")
body.appendString("Content-Disposition: form-data; name=\"image\"; filename=\"randomName\"\r\n")
body.appendString("Content-Type: image/png\r\n\r\n")
body.appendData(data)
body.appendString("\r\n")
}
and it's sending 12MB photos. How can I reduce this to 1mb? thanks!
Xcode 9 • Swift 4 or later
edit/update: For iOS10+ We can use UIGraphicsImageRenderer. For older Swift syntax check edit history.
extension UIImage {
func resized(withPercentage percentage: CGFloat, isOpaque: Bool = true) -> UIImage? {
let canvas = CGSize(width: size.width * percentage, height: size.height * percentage)
let format = imageRendererFormat
format.opaque = isOpaque
return UIGraphicsImageRenderer(size: canvas, format: format).image {
_ in draw(in: CGRect(origin: .zero, size: canvas))
}
}
func resized(toWidth width: CGFloat, isOpaque: Bool = true) -> UIImage? {
let canvas = CGSize(width: width, height: CGFloat(ceil(width/size.width * size.height)))
let format = imageRendererFormat
format.opaque = isOpaque
return UIGraphicsImageRenderer(size: canvas, format: format).image {
_ in draw(in: CGRect(origin: .zero, size: canvas))
}
}
}
Usage:
let image = UIImage(data: try! Data(contentsOf: URL(string:"http://i.stack.imgur.com/Xs4RX.jpg")!))!
let thumb1 = image.resized(withPercentage: 0.1)
let thumb2 = image.resized(toWidth: 72.0)
This is the way which i followed to resize image.
-(UIImage *)resizeImage:(UIImage *)image
{
float actualHeight = image.size.height;
float actualWidth = image.size.width;
float maxHeight = 300.0;
float maxWidth = 400.0;
float imgRatio = actualWidth/actualHeight;
float maxRatio = maxWidth/maxHeight;
float compressionQuality = 0.5;//50 percent compression
if (actualHeight > maxHeight || actualWidth > maxWidth)
{
if(imgRatio < maxRatio)
{
//adjust width according to maxHeight
imgRatio = maxHeight / actualHeight;
actualWidth = imgRatio * actualWidth;
actualHeight = maxHeight;
}
else if(imgRatio > maxRatio)
{
//adjust height according to maxWidth
imgRatio = maxWidth / actualWidth;
actualHeight = imgRatio * actualHeight;
actualWidth = maxWidth;
}
else
{
actualHeight = maxHeight;
actualWidth = maxWidth;
}
}
CGRect rect = CGRectMake(0.0, 0.0, actualWidth, actualHeight);
UIGraphicsBeginImageContext(rect.size);
[image drawInRect:rect];
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
NSData *imageData = UIImageJPEGRepresentation(img, compressionQuality);
UIGraphicsEndImageContext();
return [UIImage imageWithData:imageData];
}
Using this method my image having 6.5 MB reduced to 104 KB.
Swift 4 code:
func resize(_ image: UIImage) -> UIImage {
var actualHeight = Float(image.size.height)
var actualWidth = Float(image.size.width)
let maxHeight: Float = 300.0
let maxWidth: Float = 400.0
var imgRatio: Float = actualWidth / actualHeight
let maxRatio: Float = maxWidth / maxHeight
let compressionQuality: Float = 0.5
//50 percent compression
if actualHeight > maxHeight || actualWidth > maxWidth {
if imgRatio < maxRatio {
//adjust width according to maxHeight
imgRatio = maxHeight / actualHeight
actualWidth = imgRatio * actualWidth
actualHeight = maxHeight
}
else if imgRatio > maxRatio {
//adjust height according to maxWidth
imgRatio = maxWidth / actualWidth
actualHeight = imgRatio * actualHeight
actualWidth = maxWidth
}
else {
actualHeight = maxHeight
actualWidth = maxWidth
}
}
let rect = CGRect(x: 0.0, y: 0.0, width: CGFloat(actualWidth), height: CGFloat(actualHeight))
UIGraphicsBeginImageContext(rect.size)
image.draw(in: rect)
let img = UIGraphicsGetImageFromCurrentImageContext()
let imageData = img?.jpegData(compressionQuality: CGFloat(compressionQuality))
UIGraphicsEndImageContext()
return UIImage(data: imageData!) ?? UIImage()
}
Swift 5 & Xcode 14
I was not satisfied with the solutions here, which generate an image based on a given KB size, since most of them used .jpegData(compressionQuality: x). This method won't work with large images, since even with compression quality set to 0.0, the large image will remain large, e.g. a 10 MB produced by portrait mode of a newer iPhone still will be above 1 MB with compressionQuality set to 0.0.
Therefore I used some answers here and rewrote a Helper Struct which converts an image in a background que:
import UIKit
struct ImageCompressor {
static func compress(image: UIImage, maxByte: Int,
completion: #escaping (UIImage?) -> ()) {
DispatchQueue.global(qos: .userInitiated).async {
guard let currentImageSize = image.jpegData(compressionQuality: 1.0)?.count else {
return completion(nil)
}
var iterationImage: UIImage? = image
var iterationImageSize = currentImageSize
var iterationCompression: CGFloat = 1.0
while iterationImageSize > maxByte && iterationCompression > 0.01 {
let percentageDecrease = getPercentageToDecreaseTo(forDataCount: iterationImageSize)
let canvasSize = CGSize(width: image.size.width * iterationCompression,
height: image.size.height * iterationCompression)
UIGraphicsBeginImageContextWithOptions(canvasSize, false, image.scale)
defer { UIGraphicsEndImageContext() }
image.draw(in: CGRect(origin: .zero, size: canvasSize))
iterationImage = UIGraphicsGetImageFromCurrentImageContext()
guard let newImageSize = iterationImage?.jpegData(compressionQuality: 1.0)?.count else {
return completion(nil)
}
iterationImageSize = newImageSize
iterationCompression -= percentageDecrease
}
completion(iterationImage)
}
}
private static func getPercentageToDecreaseTo(forDataCount dataCount: Int) -> CGFloat {
switch dataCount {
case 0..<5000000: return 0.03
case 5000000..<10000000: return 0.1
default: return 0.2
}
}
}
Compress an image to max 2 MB:
ImageCompressor.compress(image: image, maxByte: 2000000) { image in
guard let compressedImage = image else { return }
// Use compressedImage
}
}
In case someone is looking for resizing image to less than 1MB with Swift 3 and 4.
Just copy&paste this extension:
extension UIImage {
func resized(withPercentage percentage: CGFloat) -> UIImage? {
let canvasSize = CGSize(width: size.width * percentage, height: size.height * percentage)
UIGraphicsBeginImageContextWithOptions(canvasSize, false, scale)
defer { UIGraphicsEndImageContext() }
draw(in: CGRect(origin: .zero, size: canvasSize))
return UIGraphicsGetImageFromCurrentImageContext()
}
func resizedTo1MB() -> UIImage? {
guard let imageData = UIImagePNGRepresentation(self) else { return nil }
var resizingImage = self
var imageSizeKB = Double(imageData.count) / 1000.0 // ! Or devide for 1024 if you need KB but not kB
while imageSizeKB > 1000 { // ! Or use 1024 if you need KB but not kB
guard let resizedImage = resizingImage.resized(withPercentage: 0.9),
let imageData = UIImagePNGRepresentation(resizedImage)
else { return nil }
resizingImage = resizedImage
imageSizeKB = Double(imageData.count) / 1000.0 // ! Or devide for 1024 if you need KB but not kB
}
return resizingImage
}
}
And use:
let resizedImage = originalImage.resizedTo1MB()
Edit:
Please note it's blocking UI, so move to background thread if you think it's the right way for your case.
same as Leo Answer but little edits for SWIFT 2.0
extension UIImage {
func resizeWithPercentage(percentage: CGFloat) -> UIImage? {
let imageView = UIImageView(frame: CGRect(origin: .zero, size: CGSize(width: size.width * percentage, height: size.height * percentage)))
imageView.contentMode = .ScaleAspectFit
imageView.image = self
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, false, scale)
guard let context = UIGraphicsGetCurrentContext() else { return nil }
imageView.layer.renderInContext(context)
guard let result = UIGraphicsGetImageFromCurrentImageContext() else { return nil }
UIGraphicsEndImageContext()
return result
}
func resizeWithWidth(width: CGFloat) -> UIImage? {
let imageView = UIImageView(frame: CGRect(origin: .zero, size: CGSize(width: width, height: CGFloat(ceil(width/size.width * size.height)))))
imageView.contentMode = .ScaleAspectFit
imageView.image = self
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, false, scale)
guard let context = UIGraphicsGetCurrentContext() else { return nil }
imageView.layer.renderInContext(context)
guard let result = UIGraphicsGetImageFromCurrentImageContext() else { return nil }
UIGraphicsEndImageContext()
return result
}
}
Swift4.2
let imagedata = yourImage.jpegData(compressionQuality: 0.1)!
Here is user4261201's answer but in swift, that I am currently using:
func compressImage (_ image: UIImage) -> UIImage {
let actualHeight:CGFloat = image.size.height
let actualWidth:CGFloat = image.size.width
let imgRatio:CGFloat = actualWidth/actualHeight
let maxWidth:CGFloat = 1024.0
let resizedHeight:CGFloat = maxWidth/imgRatio
let compressionQuality:CGFloat = 0.5
let rect:CGRect = CGRect(x: 0, y: 0, width: maxWidth, height: resizedHeight)
UIGraphicsBeginImageContext(rect.size)
image.draw(in: rect)
let img: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
let imageData:Data = UIImageJPEGRepresentation(img, compressionQuality)!
UIGraphicsEndImageContext()
return UIImage(data: imageData)!
}
I think the core of the question here is how to reliably shrink a UIImage's data to a certain size before uploading to a server, rather than just shrink the UIImage itself.
Using func jpegData(compressionQuality: CGFloat) -> Data? works well if you don't need to compress to a specific size. However, for certain cases, I find it useful to be able to compress below a certain specified file size. In that case, jpegData is unreliable, and iterative compressing of an image this way results in plateauing out on filesize (and can be really expensive). Instead, I prefer to reduce the size of the UIImage itself as in Leo's answer, then convert to jpegData and iteratively check to see if the reduced size is beneath the value I chose (within a margin that I set). I adjust the compression step multiplier based on the ratio of the current filesize to the desired filesize to speed up the first iterations which are the most expensive (since the filesize is the largest at that point).
Swift 5
extension UIImage {
func resized(withPercentage percentage: CGFloat, isOpaque: Bool = true) -> UIImage? {
let canvas = CGSize(width: size.width * percentage, height: size.height * percentage)
let format = imageRendererFormat
format.opaque = isOpaque
return UIGraphicsImageRenderer(size: canvas, format: format).image {
_ in draw(in: CGRect(origin: .zero, size: canvas))
}
}
func compress(to kb: Int, allowedMargin: CGFloat = 0.2) -> Data {
guard kb > 10 else { return Data() } // Prevents user from compressing below a limit (10kb in this case).
let bytes = kb * 1024
var compression: CGFloat = 1.0
let step: CGFloat = 0.05
var holderImage = self
var complete = false
while(!complete) {
guard let data = holderImage.jpegData(compressionQuality: 1.0) else { break }
let ratio = data.count / bytes
if data.count < Int(CGFloat(bytes) * (1 + allowedMargin)) {
complete = true
return data
} else {
let multiplier:CGFloat = CGFloat((ratio / 5) + 1)
compression -= (step * multiplier)
}
guard let newImage = holderImage.resized(withPercentage: compression) else { break }
holderImage = newImage
}
return Data()
}
}
And usage:
let data = image.compress(to: 1000)
If you are uploading image in NSData format, use this :
NSData *imageData = UIImageJPEGRepresentation(yourImage, floatValue);
yourImage is your UIImage.
floatvalue is compression value(0.0 to 1.0)
The above is to convert image to JPEG.
For PNGuse : UIImagePNGRepresentation
Note : Above code is in Objective-C. Please check how to define NSData in Swift.
Based on the answer of Tung Fam. To resize to a specific file size. Like 0.7 MB you can use this code.
extension UIImage {
func resize(withPercentage percentage: CGFloat) -> UIImage? {
var newRect = CGRect(origin: .zero, size: CGSize(width: size.width*percentage, height: size.height*percentage))
UIGraphicsBeginImageContextWithOptions(newRect.size, true, 1)
self.draw(in: newRect)
defer {UIGraphicsEndImageContext()}
return UIGraphicsGetImageFromCurrentImageContext()
}
func resizeTo(MB: Double) -> UIImage? {
guard let fileSize = self.pngData()?.count else {return nil}
let fileSizeInMB = CGFloat(fileSize)/(1024.0*1024.0)//form bytes to MB
let percentage = 1/fileSizeInMB
return resize(withPercentage: percentage)
}
}
Using this you can control the size that you want:
func jpegImage(image: UIImage, maxSize: Int, minSize: Int, times: Int) -> Data? {
var maxQuality: CGFloat = 1.0
var minQuality: CGFloat = 0.0
var bestData: Data?
for _ in 1...times {
let thisQuality = (maxQuality + minQuality) / 2
guard let data = image.jpegData(compressionQuality: thisQuality) else { return nil }
let thisSize = data.count
if thisSize > maxSize {
maxQuality = thisQuality
} else {
minQuality = thisQuality
bestData = data
if thisSize > minSize {
return bestData
}
}
}
return bestData
}
Method call example:
jpegImage(image: image, maxSize: 500000, minSize: 400000, times: 10)
It will try to get a file between a maximum and minimum size of maxSize and minSize, but only try times times. If it fails within that time, it will return nil.
I think the easiest way is provided by swift itself to compress the image into compressed data below is the code in swift 4.2
let imageData = yourImageTobeCompressed.jpegData(compressionQuality: 0.5)
and you can send this imageData to upload to server.
This is what I done in swift 3 for resizing an UIImage. It reduces the image size to less than 100kb. It works proportionally!
extension UIImage {
class func scaleImageWithDivisor(img: UIImage, divisor: CGFloat) -> UIImage {
let size = CGSize(width: img.size.width/divisor, height: img.size.height/divisor)
UIGraphicsBeginImageContext(size)
img.draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return scaledImage!
}
}
Usage:
let scaledImage = UIImage.scaleImageWithDivisor(img: capturedImage!, divisor: 3)
Same in Objective-C :
interface :
#interface UIImage (Resize)
- (UIImage *)resizedWithPercentage:(CGFloat)percentage;
- (UIImage *)resizeTo:(CGFloat)weight isPng:(BOOL)isPng jpegCompressionQuality:(CGFloat)compressionQuality;
#end
implementation :
#import "UIImage+Resize.h"
#implementation UIImage (Resize)
- (UIImage *)resizedWithPercentage:(CGFloat)percentage {
CGSize canvasSize = CGSizeMake(self.size.width * percentage, self.size.height * percentage);
UIGraphicsBeginImageContextWithOptions(canvasSize, false, self.scale);
[self drawInRect:CGRectMake(0, 0, canvasSize.width, canvasSize.height)];
UIImage *sizedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return sizedImage;
}
- (UIImage *)resizeTo:(CGFloat)weight isPng:(BOOL)isPng jpegCompressionQuality:(CGFloat)compressionQuality {
NSData *imageData = isPng ? UIImagePNGRepresentation(self) : UIImageJPEGRepresentation(self, compressionQuality);
if (imageData && [imageData length] > 0) {
UIImage *resizingImage = self;
double imageSizeKB = [imageData length] / weight;
while (imageSizeKB > weight) {
UIImage *resizedImage = [resizingImage resizedWithPercentage:0.9];
imageData = isPng ? UIImagePNGRepresentation(resizedImage) : UIImageJPEGRepresentation(resizedImage, compressionQuality);
resizingImage = resizedImage;
imageSizeKB = (double)(imageData.length / weight);
}
return resizingImage;
}
return nil;
}
Usage :
#import "UIImage+Resize.h"
UIImage *resizedImage = [self.picture resizeTo:2048 isPng:NO jpegCompressionQuality:1.0];
When I try to use the accepted answer to resize an image for use in my project it comes out very pixelated and blurry. I ended up with this piece of code to resize images without adding pixelation or blur:
func scale(withPercentage percentage: CGFloat)-> UIImage? {
let cgSize = CGSize(width: size.width * percentage, height: size.height * percentage)
let hasAlpha = true
let scale: CGFloat = 0.0 // Use scale factor of main screen
UIGraphicsBeginImageContextWithOptions(cgSize, !hasAlpha, scale)
self.draw(in: CGRect(origin: CGPoint.zero, size: cgSize))
let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
return scaledImage
}
I came across this question while investigating image compression and export in Swift, and used it as a starting point to understand the problem better & derive a better technique.
The UIGraphicsBeginImageContext(), UIGraphicsGetImageFromCurrentImageContext(), UIGraphicsEndImageContext() process is an older technique which has been superseded by UIGraphicsImageRenderer, as used by iron_john_bonney and leo-dabus. Their examples were written as extensions on UIImage, whereas I chose to write an independent function. The required differences in approach can be identified by comparison (look at and near the UIGraphicsImageRenderer call), and could easily be ported back into a UIImage extension.
I thought there was potential for improvement on the compression algorithms used here, so I took an approach that started by adjusting the image to have a given total number of pixels, and then compressing it by adjusting the jpeg compression to achieve a specified final file size. The intent of specifying a total number of pixels was to avoid getting tied up in issues with image aspect ratios. Although I haven't done an exhaustive investigation, I suspect scaling an image to a specified total number of pixels will put the final jpeg image file size in a general range, and then jpeg compression can then be used to ensure that a file size limit is achieved with acceptable image quality, providing the initial pixel count isn't too high.
When using UIGraphicsImageRenderer, the CGRect is specified in logical pixels on a host Apple device, which is different to the actual pixels in the output jpeg. Look up device pixel ratios to understand this. To obtain the device pixel ratio, I tried extracting it from the environment, but these techniques caused the playground to crash, so I used a less efficient technique that worked.
If you paste this code into an Xcode playround and place an appropriate .jpg file in the Resources folder, the output file will be placed in the Playground output folder (use Quick Look in the Live View to find this location).
import UIKit
func compressUIImage(_ image: UIImage?, numPixels: Int, fileSizeLimitKB: Double, exportImage: Bool) -> Data {
var returnData: Data
if let origWidth = image?.size.width,
let origHeight = image?.size.height {
print("Original image size =", origWidth, "*", origHeight, "pixels")
let imgMult = min(sqrt(CGFloat(numPixels)/(origWidth * origHeight)), 1) // This multiplier scales the image to have the desired number of pixels
print("imageMultiplier =", imgMult)
let cgRect = CGRect(origin: .zero, size: CGSize(width: origWidth * imgMult, height: origHeight * imgMult)) // This is in *logical* pixels
let renderer = UIGraphicsImageRenderer(size: cgRect.size)
let img = renderer.image { ctx in
image?.draw(in: cgRect)
}
// Now get the device pixel ratio if needed...
var img_scale: CGFloat = 1
if exportImage {
img_scale = img.scale
}
print("Image scaling factor =", img_scale)
// ...and use to ensure *output* image has desired number of pixels
let cgRect_scaled = CGRect(origin: .zero, size: CGSize(width: origWidth * imgMult/img_scale, height: origHeight * imgMult/img_scale)) // This is in *logical* pixels
print("New image size (in logical pixels) =", cgRect_scaled.width, "*", cgRect_scaled.height, "pixels") // Due to device pixel ratios, can have fractional pixel dimensions
let renderer_scaled = UIGraphicsImageRenderer(size: cgRect_scaled.size)
let img_scaled = renderer_scaled.image { ctx in
image?.draw(in: cgRect_scaled)
}
var compQual = CGFloat(1.0)
returnData = img_scaled.jpegData(compressionQuality: 1.0)!
var imageSizeKB = Double(returnData.count) / 1000.0
print("compressionQuality =", compQual, "=> imageSizeKB =", imageSizeKB, "KB")
while imageSizeKB > fileSizeLimitKB {
compQual *= 0.9
returnData = img_scaled.jpegData(compressionQuality: compQual)!
imageSizeKB = Double(returnData.count) / 1000.0
print("compressionQuality =", compQual, "=> imageSizeKB =", imageSizeKB, "KB")
}
} else {
returnData = Data()
}
return returnData
}
let image_orig = UIImage(named: "input.jpg")
let image_comp_data = compressUIImage(image_orig, numPixels: Int(4e6), fileSizeLimitKB: 1300, exportImage: true)
func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths[0]
}
let filename = getDocumentsDirectory().appendingPathComponent("output.jpg")
try? image_comp_data.write(to: filename)
Sources included Jordan Morgan, and Hacking with Swift.
iOS 15+ Swift 5
Part of the solutions here doesn’t answer the question because they are not producing an image that has smaller file size to upload it to backend. It is very important to not uploading big image files to backend when it is not really needed. It will take much more space, will be more expensive to store and take more time to download causing UI to wait for content.
Lots of answers is using either
UIGraphicsImageRenderer(size: canvas).image {
_ in draw(in: CGRect(origin: .zero, size: canvas))
}
Or older
UIGraphicsGetImageFromCurrentImageContext()
The problem with these solutions is they generate smaller UIImage, but are not changing underlying CGImage so when you try to send image as DATA with .jpegData(compressionQuality:) you will note upload UIImage but data from underlying CGImage which is not resized and has large file size.
The other solutions are forcing compression of jpedData to smallest available which produce very large compression and quality loss.
To actually resize image with all underlying stuff and send it as really small best quality jpeg use method preparingThumbnail(of:) and set .jpegData(compressionQuality:) to 8 or 9.
extension UIImage {
func thumbnail(width: CGFloat) -> UIImage? {
guard size.width > width else { return self }
let imageSize = CGSize(
width: width,
height: CGFloat(ceil(width/size.width * size.height))
)
return preparingThumbnail(of: imageSize)
}
}
Here is documentation
preparingThumbnail(of:)
In case someone needed, here is an async version modified from Ali Pacman's answer:
import UIKit
extension UIImage {
func compress(to maxByte: Int) async -> UIImage? {
let compressTask = Task(priority: .userInitiated) { () -> UIImage? in
guard let currentImageSize = jpegData(compressionQuality: 1.0)?.count else {
return nil
}
var iterationImage: UIImage? = self
var iterationImageSize = currentImageSize
var iterationCompression: CGFloat = 1.0
while iterationImageSize > maxByte && iterationCompression > 0.01 {
let percentageDecrease = getPercentageToDecreaseTo(forDataCount: iterationImageSize)
let canvasSize = CGSize(width: size.width * iterationCompression, height: size.height * iterationCompression)
UIGraphicsBeginImageContextWithOptions(canvasSize, false, scale)
defer { UIGraphicsEndImageContext() }
draw(in: CGRect(origin: .zero, size: canvasSize))
iterationImage = UIGraphicsGetImageFromCurrentImageContext()
guard let newImageSize = iterationImage?.jpegData(compressionQuality: 1.0)?.count else {
return nil
}
iterationImageSize = newImageSize
iterationCompression -= percentageDecrease
}
return iterationImage
}
return await compressTask.value
}
private func getPercentageToDecreaseTo(forDataCount dataCount: Int) -> CGFloat {
switch dataCount {
case 0..<3000000: return 0.05
case 3000000..<10000000: return 0.1
default: return 0.2
}
}
}
With Swift 5.5 using async/await and image.pngData() and not .jpegData(compressionQuality: 1.0) to get the correct data representation of the image:
import UIKit
public struct ImageCompressor {
private static func getPercentageToDecreaseTo(forDataCount dataCount: Int) -> CGFloat {
switch dataCount {
case 0..<3000000: return 0.05
case 3000000..<10000000: return 0.1
default: return 0.2
}
}
static public func compressAsync(image: UIImage, maxByte: Int) async -> UIImage? {
guard let currentImageSize = image.pngData()?.count else { return nil }
var iterationImage: UIImage? = image
var iterationImageSize = currentImageSize
var iterationCompression: CGFloat = 1.0
while iterationImageSize > maxByte && iterationCompression > 0.01 {
let percentageDecrease = getPercentageToDecreaseTo(forDataCount: iterationImageSize)
let canvasSize = CGSize(width: image.size.width * iterationCompression,
height: image.size.height * iterationCompression)
/*
UIGraphicsBeginImageContextWithOptions(canvasSize, false, image.scale)
defer { UIGraphicsEndImageContext() }
image.draw(in: CGRect(origin: .zero, size: canvasSize))
iterationImage = UIGraphicsGetImageFromCurrentImageContext()
*/
iterationImage = await image.byPreparingThumbnail(ofSize: canvasSize)
guard let newImageSize = iterationImage?.pngData()?.count else {
return nil
}
iterationImageSize = newImageSize
iterationCompression -= percentageDecrease
}
return iterationImage
}
}
extension UIImage {
func resized(toValue value: CGFloat) -> UIImage {
if size.width > size.height {
return self.resize(toWidth: value)!
} else {
return self.resize(toHeight: value)!
}
}
Resize the UIImage using .resizeToMaximumBytes
Related
In SwiftUI, How to educe size of Image BEFORE saving it in CoreData Attribute "Binary Data" using PhotosPicker [duplicate]
I am making an app for iOS, using Swift and Parse.com I am trying to let the user select a picture from an image picker and then resize the selected image to 200x200 pixels before uploading to my backend. Parse.com have a tutorial for an Instagram copy app called "AnyPic" which gives this code for resizing images, but it is in Objective-C.... // Resize the image to be square (what is shown in the preview) UIImage *resizedImage = [anImage resizedImageWithContentMode:UIViewContentModeScaleAspectFit bounds:CGSizeMake(560.0f, 560.0f) interpolationQuality:kCGInterpolationHigh]; // Create a thumbnail and add a corner radius for use in table views UIImage *thumbnailImage = [anImage thumbnailImage:86.0f transparentBorder:0.0f cornerRadius:10.0f interpolationQuality:kCGInterpolationDefault]; How would I create a 200x200px version of the selected picture (to then upload) in Swift? And, what is the thumbnailImage function doing?
See my blog post, Resize image in swift and objective C, for further details. Image resize function in swift as below. func resizeImage(image: UIImage, targetSize: CGSize) -> UIImage? { let size = image.size let widthRatio = targetSize.width / size.width let heightRatio = targetSize.height / size.height // Figure out what our orientation is, and use that to form the rectangle var newSize: CGSize if(widthRatio > heightRatio) { newSize = CGSize(width: size.width * heightRatio, height: size.height * heightRatio) } else { newSize = CGSize(width: size.width * widthRatio, height: size.height * widthRatio) } // This is the rect that we've calculated out and this is what is actually used below let rect = CGRect(origin: .zero, size: newSize) // Actually do the resizing to the rect using the ImageContext stuff UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0) image.draw(in: rect) let newImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return newImage } Use the above function and resize image with 200*200 as below code self.resizeImage(UIImage(named: "yourImageName")!, targetSize: CGSizeMake(200.0, 200.0)) swift3 updated func resizeImage(image: UIImage, targetSize: CGSize) -> UIImage { let size = image.size let widthRatio = targetSize.width / size.width let heightRatio = targetSize.height / size.height // Figure out what our orientation is, and use that to form the rectangle var newSize: CGSize if(widthRatio > heightRatio) { newSize = CGSize(width: size.width * heightRatio, height: size.height * heightRatio) } else { newSize = CGSize(width: size.width * widthRatio, height: size.height * widthRatio) } // This is the rect that we've calculated out and this is what is actually used below let rect = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height) // Actually do the resizing to the rect using the ImageContext stuff UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0) image.draw(in: rect) let newImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return newImage! }
Details Xcode 10.2.1 (10E1001), Swift 5 Links https://gist.github.com/eugenebokhan/5e62a0155754ae6aa6c3c13cf1744930 Image Resizing Techniques Solution import UIKit import CoreGraphics import Accelerate extension UIImage { public enum ResizeFramework { case uikit, coreImage, coreGraphics, imageIO, accelerate } /// Resize image with ScaleAspectFit mode and given size. /// /// - Parameter dimension: width or length of the image output. /// - Parameter resizeFramework: Technique for image resizing: UIKit / CoreImage / CoreGraphics / ImageIO / Accelerate. /// - Returns: Resized image. func resizeWithScaleAspectFitMode(to dimension: CGFloat, resizeFramework: ResizeFramework = .coreGraphics) -> UIImage? { if max(size.width, size.height) <= dimension { return self } var newSize: CGSize! let aspectRatio = size.width/size.height if aspectRatio > 1 { // Landscape image newSize = CGSize(width: dimension, height: dimension / aspectRatio) } else { // Portrait image newSize = CGSize(width: dimension * aspectRatio, height: dimension) } return resize(to: newSize, with: resizeFramework) } /// Resize image from given size. /// /// - Parameter newSize: Size of the image output. /// - Parameter resizeFramework: Technique for image resizing: UIKit / CoreImage / CoreGraphics / ImageIO / Accelerate. /// - Returns: Resized image. public func resize(to newSize: CGSize, with resizeFramework: ResizeFramework = .coreGraphics) -> UIImage? { switch resizeFramework { case .uikit: return resizeWithUIKit(to: newSize) case .coreGraphics: return resizeWithCoreGraphics(to: newSize) case .coreImage: return resizeWithCoreImage(to: newSize) case .imageIO: return resizeWithImageIO(to: newSize) case .accelerate: return resizeWithAccelerate(to: newSize) } } // MARK: - UIKit /// Resize image from given size. /// /// - Parameter newSize: Size of the image output. /// - Returns: Resized image. private func resizeWithUIKit(to newSize: CGSize) -> UIImage? { UIGraphicsBeginImageContextWithOptions(newSize, true, 1.0) self.draw(in: CGRect(origin: .zero, size: newSize)) defer { UIGraphicsEndImageContext() } return UIGraphicsGetImageFromCurrentImageContext() } // MARK: - CoreImage /// Resize CI image from given size. /// /// - Parameter newSize: Size of the image output. /// - Returns: Resized image. // https://developer.apple.com/library/archive/documentation/GraphicsImaging/Reference/CoreImageFilterReference/index.html private func resizeWithCoreImage(to newSize: CGSize) -> UIImage? { guard let cgImage = cgImage, let filter = CIFilter(name: "CILanczosScaleTransform") else { return nil } let ciImage = CIImage(cgImage: cgImage) let scale = (Double)(newSize.width) / (Double)(ciImage.extent.size.width) filter.setValue(ciImage, forKey: kCIInputImageKey) filter.setValue(NSNumber(value:scale), forKey: kCIInputScaleKey) filter.setValue(1.0, forKey: kCIInputAspectRatioKey) guard let outputImage = filter.value(forKey: kCIOutputImageKey) as? CIImage else { return nil } let context = CIContext(options: [.useSoftwareRenderer: false]) guard let resultCGImage = context.createCGImage(outputImage, from: outputImage.extent) else { return nil } return UIImage(cgImage: resultCGImage) } // MARK: - CoreGraphics /// Resize image from given size. /// /// - Parameter newSize: Size of the image output. /// - Returns: Resized image. private func resizeWithCoreGraphics(to newSize: CGSize) -> UIImage? { guard let cgImage = cgImage, let colorSpace = cgImage.colorSpace else { return nil } let width = Int(newSize.width) let height = Int(newSize.height) let bitsPerComponent = cgImage.bitsPerComponent let bytesPerRow = cgImage.bytesPerRow let bitmapInfo = cgImage.bitmapInfo guard let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo.rawValue) else { return nil } context.interpolationQuality = .high let rect = CGRect(origin: CGPoint.zero, size: newSize) context.draw(cgImage, in: rect) return context.makeImage().flatMap { UIImage(cgImage: $0) } } // MARK: - ImageIO /// Resize image from given size. /// /// - Parameter newSize: Size of the image output. /// - Returns: Resized image. private func resizeWithImageIO(to newSize: CGSize) -> UIImage? { var resultImage = self guard let data = jpegData(compressionQuality: 1.0) else { return resultImage } let imageCFData = NSData(data: data) as CFData let options = [ kCGImageSourceCreateThumbnailWithTransform: true, kCGImageSourceCreateThumbnailFromImageAlways: true, kCGImageSourceThumbnailMaxPixelSize: max(newSize.width, newSize.height) ] as CFDictionary guard let source = CGImageSourceCreateWithData(imageCFData, nil), let imageReference = CGImageSourceCreateThumbnailAtIndex(source, 0, options) else { return resultImage } resultImage = UIImage(cgImage: imageReference) return resultImage } // MARK: - Accelerate /// Resize image from given size. /// /// - Parameter newSize: Size of the image output. /// - Returns: Resized image. private func resizeWithAccelerate(to newSize: CGSize) -> UIImage? { var resultImage = self guard let cgImage = cgImage, let colorSpace = cgImage.colorSpace else { return nil } // create a source buffer var format = vImage_CGImageFormat(bitsPerComponent: numericCast(cgImage.bitsPerComponent), bitsPerPixel: numericCast(cgImage.bitsPerPixel), colorSpace: Unmanaged.passUnretained(colorSpace), bitmapInfo: cgImage.bitmapInfo, version: 0, decode: nil, renderingIntent: .absoluteColorimetric) var sourceBuffer = vImage_Buffer() defer { sourceBuffer.data.deallocate() } var error = vImageBuffer_InitWithCGImage(&sourceBuffer, &format, nil, cgImage, numericCast(kvImageNoFlags)) guard error == kvImageNoError else { return resultImage } // create a destination buffer let destWidth = Int(newSize.width) let destHeight = Int(newSize.height) let bytesPerPixel = cgImage.bitsPerPixel let destBytesPerRow = destWidth * bytesPerPixel let destData = UnsafeMutablePointer<UInt8>.allocate(capacity: destHeight * destBytesPerRow) defer { destData.deallocate() } var destBuffer = vImage_Buffer(data: destData, height: vImagePixelCount(destHeight), width: vImagePixelCount(destWidth), rowBytes: destBytesPerRow) // scale the image error = vImageScale_ARGB8888(&sourceBuffer, &destBuffer, nil, numericCast(kvImageHighQualityResampling)) guard error == kvImageNoError else { return resultImage } // create a CGImage from vImage_Buffer let destCGImage = vImageCreateCGImageFromBuffer(&destBuffer, &format, nil, nil, numericCast(kvImageNoFlags), &error)?.takeRetainedValue() guard error == kvImageNoError else { return resultImage } // create a UIImage if let scaledImage = destCGImage.flatMap({ UIImage(cgImage: $0) }) { resultImage = scaledImage } return resultImage } } Usage Get image size import UIKit // https://stackoverflow.com/a/55765409/4488252 extension UIImage { func getFileSizeInfo(allowedUnits: ByteCountFormatter.Units = .useMB, countStyle: ByteCountFormatter.CountStyle = .memory, compressionQuality: CGFloat = 1.0) -> String? { // https://developer.apple.com/documentation/foundation/bytecountformatter let formatter = ByteCountFormatter() formatter.allowedUnits = allowedUnits formatter.countStyle = countStyle return getSizeInfo(formatter: formatter, compressionQuality: compressionQuality) } func getSizeInfo(formatter: ByteCountFormatter, compressionQuality: CGFloat = 1.0) -> String? { guard let imageData = jpegData(compressionQuality: compressionQuality) else { return nil } return formatter.string(fromByteCount: Int64(imageData.count)) } } Test function private func test() { guard let img = UIImage(named: "img") else { return } printInfo(of: img, title: "original image |") let dimension: CGFloat = 2000 var framework: UIImage.ResizeFramework = .accelerate var startTime = Date() if let img = img.resizeWithScaleAspectFitMode(to: dimension, resizeFramework: framework) { printInfo(of: img, title: "resized image |", with: framework, startedTime: startTime) } framework = .coreGraphics startTime = Date() if let img = img.resizeWithScaleAspectFitMode(to: dimension, resizeFramework: framework) { printInfo(of: img, title: "resized image |", with: framework, startedTime: startTime) } framework = .coreImage startTime = Date() if let img = img.resizeWithScaleAspectFitMode(to: dimension, resizeFramework: framework) { printInfo(of: img, title: "resized image |", with: framework, startedTime: startTime) } framework = .imageIO startTime = Date() if let img = img.resizeWithScaleAspectFitMode(to: dimension, resizeFramework: framework) { printInfo(of: img, title: "resized image |", with: framework, startedTime: startTime) } framework = .uikit startTime = Date() if let img = img.resizeWithScaleAspectFitMode(to: dimension, resizeFramework: framework) { printInfo(of: img, title: "resized image |", with: framework, startedTime: startTime) } } private func printInfo(of image: UIImage, title: String, with resizeFramework: UIImage.ResizeFramework? = nil, startedTime: Date? = nil) { var description = "\(title) \(image.size)" if let startedTime = startedTime { description += ", execution time: \(Date().timeIntervalSince(startedTime))" } if let fileSize = image.getFileSizeInfo(compressionQuality: 0.9) { description += ", size: \(fileSize)" } if let resizeFramework = resizeFramework { description += ", framework: \(resizeFramework)" } print(description) } Output original image | (5790.0, 8687.0), size: 17.1 MB resized image | (1333.0, 2000.0), execution time: 0.8192930221557617, size: 1.1 MB, framework: accelerate resized image | (1333.0, 2000.0), execution time: 0.44696998596191406, size: 1 MB, framework: coreGraphics resized image | (1334.0, 2000.0), execution time: 54.172922015190125, size: 1.1 MB, framework: coreImage resized image | (1333.0, 2000.0), execution time: 1.8765920400619507, size: 1.1 MB, framework: imageIO resized image | (1334.0, 2000.0), execution time: 0.4638739824295044, size: 1 MB, framework: uikit
For Swift 4.0 and iOS 10 extension UIImage { func resizeImage(_ dimension: CGFloat, opaque: Bool, contentMode: UIViewContentMode = .scaleAspectFit) -> UIImage { var width: CGFloat var height: CGFloat var newImage: UIImage let size = self.size let aspectRatio = size.width/size.height switch contentMode { case .scaleAspectFit: if aspectRatio > 1 { // Landscape image width = dimension height = dimension / aspectRatio } else { // Portrait image height = dimension width = dimension * aspectRatio } default: fatalError("UIIMage.resizeToFit(): FATAL: Unimplemented ContentMode") } if #available(iOS 10.0, *) { let renderFormat = UIGraphicsImageRendererFormat.default() renderFormat.opaque = opaque let renderer = UIGraphicsImageRenderer(size: CGSize(width: width, height: height), format: renderFormat) newImage = renderer.image { (context) in self.draw(in: CGRect(x: 0, y: 0, width: width, height: height)) } } else { UIGraphicsBeginImageContextWithOptions(CGSize(width: width, height: height), opaque, 0) self.draw(in: CGRect(x: 0, y: 0, width: width, height: height)) newImage = UIGraphicsGetImageFromCurrentImageContext()! UIGraphicsEndImageContext() } return newImage } }
Since #KiritModi 's answer is from 2015, this is the Swift 3.0's version: func resizeImage(image: UIImage, targetSize: CGSize) -> UIImage { let size = image.size let widthRatio = targetSize.width / image.size.width let heightRatio = targetSize.height / image.size.height // Figure out what our orientation is, and use that to form the rectangle var newSize: CGSize if(widthRatio > heightRatio) { newSize = CGSize(width: size.width * heightRatio, height: size.height * heightRatio) } else { newSize = CGSize(width: size.width * widthRatio, height: size.height * widthRatio) } // This is the rect that we've calculated out and this is what is actually used below let rect = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height) // Actually do the resizing to the rect using the ImageContext stuff UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0) image.draw(in: rect) let newImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return newImage! }
For Swift 5.0 and iOS 12 extension UIImage { func imageResized(to size: CGSize) -> UIImage { return UIGraphicsImageRenderer(size: size).image { _ in draw(in: CGRect(origin: .zero, size: size)) } } } use: let image = #imageLiteral(resourceName: "ic_search") cell!.search.image = image.imageResized(to: cell!.search.frame.size)
For Swift 4 I would just make an extension on UIImage with referencing to self. import UIKit extension UIImage { func resizeImage(targetSize: CGSize) -> UIImage { let size = self.size let widthRatio = targetSize.width / size.width let heightRatio = targetSize.height / size.height let newSize = widthRatio > heightRatio ? CGSize(width: size.width * heightRatio, height: size.height * heightRatio) : CGSize(width: size.width * widthRatio, height: size.height * widthRatio) let rect = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height) UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0) self.draw(in: rect) let newImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return newImage! } }
It's also possible to use AlamofireImage (https://github.com/Alamofire/AlamofireImage) let size = CGSize(width: 30.0, height: 30.0) let aspectScaledToFitImage = image.af_imageAspectScaled(toFit: size) The function in the previous post gave me a blurry result.
Swift 3 Version and Extension style This answer come from #Kirit Modi. extension UIImage { func resizeImage(targetSize: CGSize) -> UIImage { let size = self.size let widthRatio = targetSize.width / size.width let heightRatio = targetSize.height / size.height // Figure out what our orientation is, and use that to form the rectangle var newSize: CGSize if(widthRatio > heightRatio) { newSize = CGSize(width: size.width * heightRatio, height: size.height * heightRatio) } else { newSize = CGSize(width: size.width * widthRatio, height: size.height * widthRatio) } // This is the rect that we've calculated out and this is what is actually used below let rect = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height) // Actually do the resizing to the rect using the ImageContext stuff UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0) self.draw(in: rect) let newImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return newImage! } }
Updated Swift 5 version that uses the latest UIGraphicsImageRenderer API. extension UIImage { public func resized(to target: CGSize) -> UIImage { let ratio = min( target.height / size.height, target.width / size.width ) let new = CGSize( width: size.width * ratio, height: size.height * ratio ) let renderer = UIGraphicsImageRenderer(size: new) return renderer.image { _ in self.draw(in: CGRect(origin: .zero, size: new)) } } }
Swift 4, extension version, NO WHITE LINE ON EDGES. Nobody seems to be mentioning that if image.draw() is called with non-integer values, resulting image could show a white line artifact at the right or bottom edge. extension UIImage { func scaled(with scale: CGFloat) -> UIImage? { // size has to be integer, otherwise it could get white lines let size = CGSize(width: floor(self.size.width * scale), height: floor(self.size.height * scale)) UIGraphicsBeginImageContext(size) draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height)) let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return image }
Swift 5 version respecting ratio (scaleToFill) and centering image: extension UIImage { func resized(to newSize: CGSize) -> UIImage { return UIGraphicsImageRenderer(size: newSize).image { _ in let hScale = newSize.height / size.height let vScale = newSize.width / size.width let scale = max(hScale, vScale) // scaleToFill let resizeSize = CGSize(width: size.width*scale, height: size.height*scale) var middle = CGPoint.zero if resizeSize.width > newSize.width { middle.x -= (resizeSize.width-newSize.width)/2.0 } if resizeSize.height > newSize.height { middle.y -= (resizeSize.height-newSize.height)/2.0 } draw(in: CGRect(origin: middle, size: resizeSize)) } } }
Swift 4 Version extension UIImage { func resizeImage(_ newSize: CGSize) -> UIImage? { func isSameSize(_ newSize: CGSize) -> Bool { return size == newSize } func scaleImage(_ newSize: CGSize) -> UIImage? { func getScaledRect(_ newSize: CGSize) -> CGRect { let ratio = max(newSize.width / size.width, newSize.height / size.height) let width = size.width * ratio let height = size.height * ratio return CGRect(x: 0, y: 0, width: width, height: height) } func _scaleImage(_ scaledRect: CGRect) -> UIImage? { UIGraphicsBeginImageContextWithOptions(scaledRect.size, false, 0.0); draw(in: scaledRect) let image = UIGraphicsGetImageFromCurrentImageContext() ?? UIImage() UIGraphicsEndImageContext() return image } return _scaleImage(getScaledRect(newSize)) } return isSameSize(newSize) ? self : scaleImage(newSize)! } }
UIImage Extension Swift 5 extension UIImage { func resize(_ width: CGFloat, _ height:CGFloat) -> UIImage? { let widthRatio = width / size.width let heightRatio = height / size.height let ratio = widthRatio > heightRatio ? heightRatio : widthRatio let newSize = CGSize(width: size.width * ratio, height: size.height * ratio) let rect = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height) UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0) self.draw(in: rect) let newImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return newImage } } Use : UIImage().resize(200, 300)
All of the listed answers so far seem to result in an image of a reduced size, however the size isn't measured in pixels. Here's a Swift 5, pixel-based resize. extension UIImage { func resize(_ max_size: CGFloat) -> UIImage { // adjust for device pixel density let max_size_pixels = max_size / UIScreen.main.scale // work out aspect ratio let aspectRatio = size.width/size.height // variables for storing calculated data var width: CGFloat var height: CGFloat var newImage: UIImage if aspectRatio > 1 { // landscape width = max_size_pixels height = max_size_pixels / aspectRatio } else { // portrait height = max_size_pixels width = max_size_pixels * aspectRatio } // create an image renderer of the correct size let renderer = UIGraphicsImageRenderer(size: CGSize(width: width, height: height), format: UIGraphicsImageRendererFormat.default()) // render the image newImage = renderer.image { (context) in self.draw(in: CGRect(x: 0, y: 0, width: width, height: height)) } // return the image return newImage } } Usage: image.resize(500)
Here's a general method (in Swift 5) for downscaling an image to fit a size. The resulting image can have the same aspect ratio as the original, or it can be the target size with the original image centered in it. If the image is smaller than the target size, it is not resized. extension UIImage { func scaledDown(into size:CGSize, centered:Bool = false) -> UIImage { var (targetWidth, targetHeight) = (self.size.width, self.size.height) var (scaleW, scaleH) = (1 as CGFloat, 1 as CGFloat) if targetWidth > size.width { scaleW = size.width/targetWidth } if targetHeight > size.height { scaleH = size.height/targetHeight } let scale = min(scaleW,scaleH) targetWidth *= scale; targetHeight *= scale let sz = CGSize(width:targetWidth, height:targetHeight) if !centered { return UIGraphicsImageRenderer(size:sz).image { _ in self.draw(in:CGRect(origin:.zero, size:sz)) } } let x = (size.width - targetWidth)/2 let y = (size.height - targetHeight)/2 let origin = CGPoint(x:x,y:y) return UIGraphicsImageRenderer(size:size).image { _ in self.draw(in:CGRect(origin:origin, size:sz)) } } }
Swift 4 Solution- Use this function func image(with image: UIImage, scaledTo newSize: CGSize) -> UIImage { UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0) image.draw(in: CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height)) let newImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() drawingImageView.image = newImage return newImage ?? UIImage() } Calling a function:- image(with: predictionImage, scaledTo: CGSize(width: 28.0, height: 28.0) here 28.0 is the pixel size that you want to set
Swift 4.2 version of #KiritModi answer func resizeImage(image: UIImage, targetSize: CGSize) -> UIImage { let size = image.size let widthRatio = targetSize.width / size.width let heightRatio = targetSize.height / size.height var newSize: CGSize if(widthRatio > heightRatio) { newSize = CGSize(width: size.width * heightRatio, height: size.height * heightRatio) } else { newSize = CGSize(width: size.width * widthRatio, height: size.height * widthRatio) } let rect = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height) UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0) image.draw(in: rect) let newImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return newImage! }
calling of resizeimage method let image1 = resizeimage(image: myimage.image!, withSize: CGSize(width:200, height: 200)) method for resizeing image func resizeimage(image:UIImage,withSize:CGSize) -> UIImage { var actualHeight:CGFloat = image.size.height var actualWidth:CGFloat = image.size.width let maxHeight:CGFloat = withSize.height let maxWidth:CGFloat = withSize.width var imgRatio:CGFloat = actualWidth/actualHeight let maxRatio:CGFloat = maxWidth/maxHeight let compressionQuality = 0.5 if (actualHeight>maxHeight||actualWidth>maxWidth) { if (imgRatio<maxRatio){ //adjust width according to maxHeight imgRatio = maxHeight/actualHeight actualWidth = imgRatio * actualWidth actualHeight = maxHeight }else if(imgRatio>maxRatio){ // adjust height according to maxWidth imgRatio = maxWidth/actualWidth actualHeight = imgRatio * actualHeight actualWidth = maxWidth }else{ actualHeight = maxHeight actualWidth = maxWidth } } let rec:CGRect = CGRect(x:0.0,y:0.0,width:actualWidth,height:actualHeight) UIGraphicsBeginImageContext(rec.size) image.draw(in: rec) let image:UIImage = UIGraphicsGetImageFromCurrentImageContext()! let imageData = UIImageJPEGRepresentation(image, CGFloat(compressionQuality)) UIGraphicsEndImageContext() let resizedimage = UIImage(data: imageData!) return resizedimage! }
Here you have two simple functions of UIImage extension: func scaledWithMaxWidthOrHeightValue(value: CGFloat) -> UIImage? { let width = self.size.width let height = self.size.height let ratio = width/height var newWidth = value var newHeight = value if ratio > 1 { newWidth = width * (newHeight/height) } else { newHeight = height * (newWidth/width) } UIGraphicsBeginImageContextWithOptions(CGSize(width: newWidth, height: newHeight), false, 0) draw(in: CGRect(x: 0, y: 0, width: newWidth, height: newHeight)) let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return image } func scaled(withScale scale: CGFloat) -> UIImage? { let size = CGSize(width: self.size.width * scale, height: self.size.height * scale) UIGraphicsBeginImageContextWithOptions(size, false, 0) draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height)) let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return image }
SWIFT 5 - XCODE 12 -- RESIZE IMAGE & No White line I used a wonderful solution above for Swift 5. And I changed one bit to include the term "floor" as I was getting a white line around my resized images. This rounds it to the nearest pixel or something so it looks great! I also had to change the syntax around the image name when the function is called (last line). //method for resizing image func resizeimage(image:UIImage,withSize:CGSize) -> UIImage { var actualHeight:CGFloat = image.size.height var actualWidth:CGFloat = image.size.width let maxHeight:CGFloat = withSize.height let maxWidth:CGFloat = withSize.width var imgRatio:CGFloat = actualWidth/actualHeight let maxRatio:CGFloat = maxWidth/maxHeight let compressionQuality = 0.5 if (actualHeight>maxHeight||actualWidth>maxWidth) { if (imgRatio<maxRatio){ //adjust width according to maxHeight imgRatio = maxHeight/actualHeight actualWidth = floor(imgRatio * actualWidth) actualHeight = maxHeight }else if(imgRatio>maxRatio){ // adjust height according to maxWidth imgRatio = maxWidth/actualWidth actualHeight = imgRatio * actualHeight actualWidth = maxWidth }else{ actualHeight = maxHeight actualWidth = maxWidth } } let rec:CGRect = CGRect(x:0.0,y:0.0,width:actualWidth,height:actualHeight) UIGraphicsBeginImageContext(rec.size) image.draw(in: rec) let image:UIImage = UIGraphicsGetImageFromCurrentImageContext()! let imageData = UIImageJPEGRepresentation(image, CGFloat(compressionQuality)) UIGraphicsEndImageContext() let resizedimage = UIImage(data: imageData!) return resizedimage! } //calling of resizeimage method: let myimage = UIImage(named: "imagename") let image1 = resizeimage(image: myimage!, withSize: CGSize(width:50, height: 50)).withRenderingMode(.alwaysOriginal)
Example is for image minimize to 1024 and less func resizeImage(image: UIImage) -> UIImage { if image.size.height >= 1024 && image.size.width >= 1024 { UIGraphicsBeginImageContext(CGSize(width:1024, height:1024)) image.draw(in: CGRect(x:0, y:0, width:1024, height:1024)) let newImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return newImage! } else if image.size.height >= 1024 && image.size.width < 1024 { UIGraphicsBeginImageContext(CGSize(width:image.size.width, height:1024)) image.draw(in: CGRect(x:0, y:0, width:image.size.width, height:1024)) let newImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return newImage! } else if image.size.width >= 1024 && image.size.height < 1024 { UIGraphicsBeginImageContext(CGSize(width:1024, height:image.size.height)) image.draw(in: CGRect(x:0, y:0, width:1024, height:image.size.height)) let newImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return newImage! } else { return image } }
You can use this for fit image at Swift 3; extension UIImage { func resizedImage(newSize: CGSize) -> UIImage { // Guard newSize is different guard self.size != newSize else { return self } UIGraphicsBeginImageContextWithOptions(newSize, false, 0.0); self.draw(in: CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height)) let newImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()! UIGraphicsEndImageContext() return newImage } func resizedImageWithinRect(rectSize: CGSize) -> UIImage { let widthFactor = size.width / rectSize.width let heightFactor = size.height / rectSize.height var resizeFactor = widthFactor if size.height > size.width { resizeFactor = heightFactor } let newSize = CGSize(width: size.width/resizeFactor, height: size.height/resizeFactor) let resized = resizedImage(newSize: newSize) return resized } } Usage; let resizedImage = image.resizedImageWithinRect(rectSize: CGSize(width: 1900, height: 1900))
How to merge 3 images(100x100px) to one new large (300x100px) in Swift
iam very new to swiftUI and dont found a way. I want to create one image (300x100) dyncmicaly by merging three single 100x100 images horizontaly ImageA(100x100) + ImageB(100x100) + ImageC(100x100) = ImageD(300x100) Found a way to show them in a HStack but how can is get one new image to send the Date to new function Regards Alex
thanks a lot, I tried to use your function but got an error here "Cannot convert value of type 'UIImage?' to expected element type 'UIImage'! Code should drat 3x zero. in the number-0.png ist just one lagre zero // // ContentView.swift // imagecombine // // Created by Alex on 02.08.20. // Copyright © 2020 Alex. All rights reserved. // import SwiftUI func combineHorizontally(_ images: [UIImage]) -> UIImage? { guard !images.isEmpty else { return nil } var size = CGSize.zero var scale = CGFloat.zero for image in images { scale = max(scale, image.scale) size = CGSize(width: size.width + image.size.width, height: max(size.height, image.size.height)) } var position = CGPoint.zero let format = UIGraphicsImageRendererFormat() format.scale = scale return UIGraphicsImageRenderer(size: size, format: format).image { context in for image in images { image.draw(at: position) position.x += image.size.width } } } let redImage = UIImage(named: "number-0.png") let greenImage = UIImage(named: "number-0.png") let blueImage = UIImage(named: "number-0.png") let image = combineHorizontally([redImage, greenImage, blueImage]) struct ContentView: View { var body: some View { Image(image) } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
You can use UIImageGraphicsRenderer and draw the images one after another: func combineHorizontally(_ images: [UIImage]) -> UIImage? { guard !images.isEmpty else { return nil } var size = CGSize.zero var scale = CGFloat.zero for image in images { scale = max(scale, image.scale) size = CGSize(width: size.width + image.size.width, height: max(size.height, image.size.height)) } var position = CGPoint.zero let format = UIGraphicsImageRendererFormat() format.scale = scale return UIGraphicsImageRenderer(size: size, format: format).image { _ in for image in images { image.draw(at: position) position.x += image.size.width } } } You said that there were only images and they were 100 × 100, but the above should work regardless of the number and size (memory permitting, of course). Anyway, this let image = combineHorizontally([redImage, greenImage, blueImage]) Results in: To use that in a context where you don’t want an optional, you can use ! forced unwrapping operator, ?? nil coalescing operator, or some other unwrapping pattern, e.g. guard let, if let, etc. Alternatively, if you don’t want to deal with optionals at all, you can write a rendition that doesn’t return an optional at all (but also doesn’t detect the error scenario where an empty array was provided): func combineHorizontally(_ images: [UIImage]) -> UIImage { var size = CGSize.zero var scale: CGFloat = 1 for image in images { scale = max(scale, image.scale) size = CGSize(width: size.width + image.size.width, height: max(size.height, image.size.height)) } var position = CGPoint.zero let format = UIGraphicsImageRendererFormat() format.scale = scale return UIGraphicsImageRenderer(size: size, format: format).image { _ in for image in images { image.draw(at: position) position.x += image.size.width } } }
iOS Swift how to incrementally draw to a UIImage?
How can I use iOS core graphics to incrementally draw a large data set in a single image? I have code which is ready to process the entire dataset at once (over 100,000 rectangles) and produces a single image. This is a very long running operation and I want this dataset to be incrementally drawn 1000 rectangles at a time, displaying these small image updates (like images downloaded from internet in the 90s) My questions are: Would I keep the reference to the same context throughout the operation and simply add elements to it? - OR - Should I be capturing the current image using UIGraphicsGetImageFromCurrentImageContext() , then drawing it in a new context and drawing additional rectangles on top of it? Bonus question - is this the right approach if I want to use multiple threads to append to the same image? let context = UIGraphicsGetCurrentContext()! context.setStrokeColor(borderColor) context.setLineWidth(CGFloat(borderWidth)) for elementIndex in 0 ..< data.count { context.setFillColor(color.cgColor) let marker = CGRect(x: toX(elementIndex), y: toY(elementIndex), width: rectWidth, height: rectHeight) context.addRect(marker) context.drawPath(using: .fillStroke) } // Save the context as a new UIImage let myImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() if let cgImage = myImage?.cgImage, let orientation = myImage?.imageOrientation { return UIImage(cgImage: cgImage, scale: 2, orientation: orientation) }
You should: Dispatch the whole thing to some background queue; periodically call UIGraphicsGetImageFromCurrentImageContext and dispatch the image view update to the main queue E.g., this will update the image view every ¼ second: DispatchQueue.global().async { var lastDrawn = CACurrentMediaTime() UIGraphicsBeginImageContextWithOptions(size, false, 0) for _ in 0 ..< 100_000 { // draw whatever you want let now = CACurrentMediaTime() if now - lastDrawn > 0.25 { self.updateImageView() lastDrawn = now } } self.updateImageView() UIGraphicsEndImageContext() } Where: func updateImageView() { guard let image = UIGraphicsGetImageFromCurrentImageContext() else { return } DispatchQueue.main.async { self.imageView.image = image } } Thus: func buildImage(of size: CGSize) { DispatchQueue.global().async { var lastDrawn = CACurrentMediaTime() UIGraphicsBeginImageContextWithOptions(size, false, 0) for _ in 0 ..< 100_000 { self.someColor().setFill() UIBezierPath(rect: self.someRectangle(in: size)).fill() let now = CACurrentMediaTime() if now - lastDrawn > 0.25 { self.updateImageView() lastDrawn = now } } self.updateImageView() UIGraphicsEndImageContext() } } func updateImageView() { let image = UIGraphicsGetImageFromCurrentImageContext() DispatchQueue.main.async { self.imageView.image = image } } func someRectangle(in size: CGSize) -> CGRect { let x = CGFloat.random(in: 0...size.width) let y = CGFloat.random(in: 0...size.height) let width = CGFloat.random(in: 0...(size.width - x)) let height = CGFloat.random(in: 0...(size.height - y)) return CGRect(x: x, y: y, width: width, height: height) } func someColor() -> UIColor { return UIColor(red: .random(in: 0...1), green: .random(in: 0...1), blue: .random(in: 0...1), alpha: 1) } Yields: Now, I’m not calling CoreGraphics directly, but you can and it will work the same.
Capturing MTKView texture to UIImageView efficiently
I want to capture the contents of the portion of an MTKView that has most recently updated into an UIImageView. I the following piece of code to accomplish this task: let cicontext = CIContext(mtlDevice: self.device!) // set up once along with rest of renderPipeline ... let lastSubStrokeCIImage = CIImage(mtlTexture: lastDrawableDisplayed.texture, options: nil)!.oriented(CGImagePropertyOrientation.downMirrored) let bboxChunkSubCurvesScaledAndYFlipped = CGRect(...) // get bounding box of region just drawn let imageCropCG = cicontext.createCGImage(lastSubStrokeCIImage, from: bboxStrokeAccumulatingScaledAndYFlipped) // Now that we have a CGImage of just the right size, we have to do the following expensive operations before assigning to a UIIView UIGraphicsBeginImageContextWithOptions(bboxStrokeAccumulating.size, false, 0) // open a bboxKeyframe-sized context UIGraphicsGetCurrentContext()?.translateBy(x: 0, y: bboxStrokeAccumulating.height) UIGraphicsGetCurrentContext()?.scaleBy(x: 1.0, y: -1.0) UIGraphicsGetCurrentContext()?.draw(imageCropCG!, in: CGRect(x: 0, y: 0 , width: bboxStrokeAccumulating.width, height: bboxStrokeAccumulating.height)) // Okay, finally we create a CALayer to be a container for what we've just drawn let layerStroke = CALayer() layerStroke.frame = bboxStrokeAccumulating layerStroke.contents = UIGraphicsGetImageFromCurrentImageContext()?.cgImage UIGraphicsEndImageContext() strokeView.layer.sublayers = nil // empty out strokeView strokeView.layer.addSublayer(layerStroke) // add our hard-earned drawing in its latest state So, this code works, but is not very efficient and makes the app lag when bboxStrokeAccumulating gets very large. Can anyone suggest more efficient alternatives?
For swift 4.0, let lastDrawableDisplayed = metalView?.currentDrawable?.texture if let imageRef = lastDrawableDisplayed?.toImage() { let uiImage:UIImage = UIImage.init(cgImage: imageRef) } extension MTLTexture { func bytes() -> UnsafeMutableRawPointer { let width = self.width let height = self.height let rowBytes = self.width * 4 let p = malloc(width * height * 4) self.getBytes(p!, bytesPerRow: rowBytes, from: MTLRegionMake2D(0, 0, width, height), mipmapLevel: 0) return p! } func toImage() -> CGImage? { let p = bytes() let pColorSpace = CGColorSpaceCreateDeviceRGB() let rawBitmapInfo = CGImageAlphaInfo.noneSkipFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue let bitmapInfo:CGBitmapInfo = CGBitmapInfo(rawValue: rawBitmapInfo) let selftureSize = self.width * self.height * 4 let rowBytes = self.width * 4 let releaseMaskImagePixelData: CGDataProviderReleaseDataCallback = { (info: UnsafeMutableRawPointer?, data: UnsafeRawPointer, size: Int) -> () in return } let provider = CGDataProvider(dataInfo: nil, data: p, size: selftureSize, releaseData: releaseMaskImagePixelData) let cgImageRef = CGImage(width: self.width, height: self.height, bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: rowBytes, space: pColorSpace, bitmapInfo: bitmapInfo, provider: provider!, decode: nil, shouldInterpolate: true, intent: CGColorRenderingIntent.defaultIntent)! return cgImageRef } } you can set the uiImage to your uiImageView
Get NSTextField contents to scale
How can I have the text scale to fit the bounds I gave it?
I've done something like this in the past. -(void)calcFontSizeToFitRect:(NSRect)r { float targetWidth = r.size.width - xMargin; float targetHeight = r.size.height - yMargin; // the strategy is to start with a small font size and go larger until I'm larger than one of the target sizes int i; for (i=minFontSize; i<maxFontSize; i++) { NSDictionary* attrs = [[NSDictionary alloc] initWithObjectsAndKeys:[NSFont fontWithName:currentFontName size:i], NSFontAttributeName, nil]; NSSize strSize = [stringValue sizeWithAttributes:attrs]; [attrs release]; if (strSize.width > targetWidth || strSize.height > targetHeight) break; } [self setCurrentFontSize:(i-1)]; } The stringValue variable is the text you want sized. The xMargin and yMargin variables are for spacing that you want. The minFontSize and maxFontSize variables give limits to how small or large you want to go.
This solution appropriated from iOS works quite well. However, one thing to note: If you are setting this up programatically, you need to initialise your NSTextfield with a rect that has a width and height, otherwise the bounds returns 0. Also here's the link where I found this solution: https://medium.com/#joncardasis/dynamic-text-resizing-in-swift-3da55887beb3 extension NSFont { /** Will return the best font conforming to the descriptor which will fit in the provided bounds. */ static func bestFittingFontSize(for text: String, in bounds: CGRect, fontDescriptor: NSFontDescriptor, additionalAttributes: [NSAttributedString.Key: Any]? = nil) -> CGFloat { let constrainingDimension = min(bounds.width, bounds.height) let properBounds = CGRect(origin: .zero, size: bounds.size) var attributes = additionalAttributes ?? [:] let infiniteBounds = CGSize(width: CGFloat.infinity, height: CGFloat.infinity) var bestFontSize: CGFloat = constrainingDimension for fontSize in stride(from: bestFontSize, through: 0, by: -1) { let newFont = NSFont(descriptor: fontDescriptor, size: fontSize) attributes[.font] = newFont let currentFrame = text.boundingRect(with: infiniteBounds, options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: attributes, context: nil) if properBounds.contains(currentFrame) { bestFontSize = fontSize break } } return bestFontSize } static func bestFittingFont(for text: String, in bounds: CGRect, fontDescriptor: NSFontDescriptor, additionalAttributes: [NSAttributedString.Key: Any]? = nil) -> NSFont { let bestSize = bestFittingFontSize(for: text, in: bounds, fontDescriptor: fontDescriptor, additionalAttributes: additionalAttributes) // TODO: Safely unwrap this later return NSFont(descriptor: fontDescriptor, size: bestSize)! } } extension NSTextField { /// Will auto resize the contained text to a font size which fits the frames bounds. /// Uses the pre-set font to dynamically determine the proper sizing func fitTextToBounds() { guard let currentFont = font else { return } let text = stringValue let bestFittingFont = NSFont.bestFittingFont(for: text, in: bounds, fontDescriptor: currentFont.fontDescriptor, additionalAttributes: basicStringAttributes) font = bestFittingFont } private var basicStringAttributes: [NSAttributedString.Key: Any] { var attribs = [NSAttributedString.Key: Any]() let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.alignment = self.alignment paragraphStyle.lineBreakMode = self.lineBreakMode attribs[.paragraphStyle] = paragraphStyle return attribs } }
For me label.adjustsFontSizeToFitWidth = true reduces the font size. with... lazy var labelContainerView: UIView = { let view = UIView() return view.labelContainerView(view: view, label) }() extension UIView { func anchor( top: NSLayoutYAxisAnchor?, left: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, right: NSLayoutXAxisAnchor?, paddingTop: CGFloat, paddingLeft: CGFloat, paddingBottom: CGFloat, paddingRight: CGFloat, width: CGFloat, height: CGFloat ) { translatesAutoresizingMaskIntoConstraints = false if let top = top { self.topAnchor.constraint(equalTo: top, constant: paddingTop).isActive = true } if let left = left { self.leftAnchor.constraint(equalTo: left, constant: paddingLeft).isActive = true } if let bottom = bottom { self.bottomAnchor.constraint(equalTo: bottom, constant: -paddingBottom).isActive = true } if let right = right { self.rightAnchor.constraint(equalTo: right, constant: -paddingRight).isActive = true } if width != 0 { widthAnchor.constraint(equalToConstant: width).isActive = true } if height != 0 { heightAnchor.constraint(equalToConstant: height).isActive = true } } } func labelContainerView(view: UIView, _ label: UILabel) -> UIView { view.addSubview(label) label.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: nil, right: nil, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0) label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true label.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true return view } }