Hemang
Hemang

Reputation: 27050

How to merge two UIImages while keeping their position, size and aspect ratios?

I am trying to merge two UIImages 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


I am showing A and B in following UIImageViews.

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.


This is the merging code:

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

Answers (1)

mixel
mixel

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

Related Questions