Reputation: 27050
I am trying to merge two UIImage
s of different sizes.
I have UIImage A
is of the following size: 1287 × 1662
pixels
And UIImage B
is of the following size: 200 × 200
pixels
UIImageView
s.
UIImageView
backgroundImageView of the size: 375 x 667
And UIImageView
foregroundImageView of the size: 100 x 100
User can move foregroundImageView
to any position above the backgroundImageView
.
let previewImage:UIImage? = mergeImages(img: imgBackground.image!, sizeWaterMark: CGRect.init(origin: imgForeground.frame.origin, size: CGSize.init(width: 100, height: 100)), waterMarkImage: imgForeground.image!)
func mergeImages(img:UIImage, sizeWaterMark:CGRect, waterMarkImage:UIImage) -> UIImage {
let size = self.imgBackground.frame.size
UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale)
img.draw(in: getAspectFitFrame(sizeImgView: size, sizeImage: img.size))
let frameAspect:CGRect = getAspectFitFrame(sizeImgView: sizeWaterMark.size, sizeImage: waterMarkImage.size)
let frameOrig:CGRect = CGRect(x: sizeWaterMark.origin.x+frameAspect.origin.x, y: sizeWaterMark.origin.y+frameAspect.origin.y, width: frameAspect.size.width, height: frameAspect.size.height)
waterMarkImage.draw(in: frameOrig, blendMode: .normal, alpha: 1)
let result:UIImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return result
}
func getAspectFitFrame(sizeImgView:CGSize, sizeImage:CGSize) -> CGRect {
let imageSize:CGSize = sizeImage
let viewSize:CGSize = sizeImgView
let hfactor : CGFloat = imageSize.width/viewSize.width
let vfactor : CGFloat = imageSize.height/viewSize.height
let factor : CGFloat = max(hfactor, vfactor)
// Divide the size by the greater of the vertical or horizontal shrinkage factor
let newWidth : CGFloat = imageSize.width / factor
let newHeight : CGFloat = imageSize.height / factor
var x:CGFloat = 0.0
var y:CGFloat = 0.0
if hfactor > vfactor {
y = (sizeImgView.height - newHeight) / 2
} else {
x = (sizeImgView.width - newWidth) / 2
}
let newRect:CGRect = CGRect(x: x, y: y, width: newWidth, height: newHeight)
return newRect
}
This is actually merging and giving me what I am looking for. But it's reducing the size of merged image. As you can see this line in the mergeImages
function.
let size = self.imgBackground.frame.size
I want the size should be the original UIImage A
size. So if I change it to this,
let size = self.imgBackground.image!.size
This will change the location of the B over A, after merging.
For testing, you can download and check the source code from here.
What should I do to keep the original size as it is while having the exact position of B over A with proper aspect ratio?
Upvotes: 2
Views: 343
Reputation: 25846
I made utility functions static (it's even better to move them in separate file) to be sure that they are not using ViewController
instance properties and methods.
In mergeImages
I removed:
let size = self.imgBackground.frame.size
and replaced size
with img.size
. It's the same as using self.imgBackground.image!.size
as you described in question.
Because source and target image sizes are the same there is no need to adjust aspect and we simply replace:
img.draw(in: getAspectFitFrame(sizeImgView: size, sizeImage: img.size))
with
img.draw(in: CGRect(origin: CGPoint(x: 0, y: 0), size: img.size))
Also I extracted aspect factor calculation to separate function getFactor
to make code more granular and made getAspectFitFrame
return not only CGRect
but also aspect factor (it'll be useful later).
So utility functions are now looking like:
static func mergeImages(img: UIImage, sizeWaterMark: CGRect, waterMarkImage: UIImage) -> UIImage {
UIGraphicsBeginImageContextWithOptions(img.size, false, UIScreen.main.scale)
img.draw(in: CGRect(origin: CGPoint(x: 0, y: 0), size: img.size))
let (frameAspect, _) = getAspectFitFrame(from: sizeWaterMark.size, to: waterMarkImage.size)
let frameOrig = CGRect(x: sizeWaterMark.origin.x + frameAspect.origin.x, y: sizeWaterMark.origin.y + frameAspect.origin.y, width: frameAspect.size.width, height: frameAspect.size.height)
waterMarkImage.draw(in: frameOrig, blendMode: .normal, alpha: 1)
let result = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return result
}
static func getAspectFitFrame(from: CGSize, to: CGSize) -> (CGRect, CGFloat) {
let (hfactor, vfactor, factor) = ViewController.getFactor(from: from, to: to)
// Divide the size by the greater of the vertical or horizontal shrinkage factor
let newWidth = to.width / factor
let newHeight = to.height / factor
var x: CGFloat = 0.0
var y: CGFloat = 0.0
if hfactor > vfactor {
y = (from.height - newHeight) / 2
} else {
x = (from.width - newWidth) / 2
}
return (CGRect(x: x, y: y, width: newWidth, height: newHeight), factor)
}
static func getFactor(from: CGSize, to: CGSize) -> (CGFloat, CGFloat, CGFloat) {
let hfactor = to.width / from.width
let vfactor = to.height / from.height
return (hfactor, vfactor, max(hfactor, vfactor))
}
Also you need another utility function to calculate scaled water mark origin and size:
static func getScaledFrame(from: CGSize, to: CGSize, target: CGRect) -> CGRect {
let (aspectFitFrame, factor) = ViewController.getAspectFitFrame(from: from, to: to)
return CGRect(
origin: CGPoint(
x: (target.origin.x - aspectFitFrame.origin.x) * factor,
y: (target.origin.y - aspectFitFrame.origin.y) * factor),
size: CGSize(width: target.width * factor, height: target.height * factor)
)
}
Now you are ready to render merged image:
let previewImage = ViewController.mergeImages(
img: imgBackground.image!,
sizeWaterMark: ViewController.getScaledFrame(from: imgBackground.frame.size, to: imgBackground.image!.size, target: imgForeground.frame),
waterMarkImage: imgForeground.image!
)
Upvotes: 2