Greg Ortega
Greg Ortega

Reputation: 11

Rotate and blend image in iOS swift

How can I obtain an image that is blended with an image underneath and rotated around its center?

Im trying to show an image that is blended with whatever view is underneath that image. In my app the user is able to move, expand and rotate the image that is blending.

To do this I have one main imageView, and on top one smaller imageview that is used for the blending. The blending imageview image property is updated every time it's position changes.

The blending of the image works fine as long as the image is not rotated, as soon as the rotation changes, the image that is returned by my function is not at the correct position.

I have tried 2 different versions of the same function.

Version 1: updateBlendingView

  1. create context with size of blending imageview
  2. draw the main imageview at an offset so that the context is at the position of the blending imageview.
  3. move the context to its the center
  4. rotate the context to the rotation of the blending imageview
  5. draw the blending imageview with blendMode
  6. get image from context and update imageview

Version 2: updateBlendingView2

  1. create context with size of blending imageview
  2. move context to its the center
  3. inversely rotate the context
  4. draw the main imageview at an offset from the center
  5. undo the rotate
  6. draw the blending view at it's origin offset from origin of the context
  7. get image from context and update blending imageview

Current result:

enter image description here

Expected result:

enter image description here

Note: keep in mind the intensity of the blending in sketch might differ from the app, but the issue is not that, is about the image positioning :)

func updateBlendingView() {

    guard let mainViewImage = getImageFromView(view: mainImageView) else {return}

    // Reset image to original state before blending
    blendingImageView.image = UIImage(named: "square")!
    guard let blendingImage = getImageFromView(view: blendingImageView) else {return}

    UIGraphicsBeginImageContextWithOptions(blendingImageView.bounds.size, true, UIScreen.main.scale)
    guard let context = UIGraphicsGetCurrentContext() else { UIGraphicsEndImageContext();return}

    let blendingOriginTransform = CGAffineTransform(translationX: (blendingImageView.bounds.width * 0.5), y: (blendingImageView.bounds.height * 0.5))

    let radians:Float = atan2f(Float(blendingImageView.transform.b), Float(blendingImageView.transform.a))
    let rotateTransform: CGAffineTransform = CGAffineTransform(rotationAngle: CGFloat(radians))

    let mainViewOrigin = CGPoint(x: -blendingImageView.frame.origin.x , y: -blendingImageView.frame.origin.y )
    mainViewImage.draw(at: mainViewOrigin)

    context.concatenate(blendingOriginTransform)
    context.concatenate(rotateTransform)


    let blendingDrawingOrigin = CGPoint(x: -blendingImageView.bounds.width * 0.5, y: -blendingImageView.bounds.height * 0.5)
    blendingImage.draw(at: blendingDrawingOrigin, blendMode: .multiply , alpha: 0.5)

    guard let blendedImage = UIGraphicsGetImageFromCurrentImageContext() else {
        UIGraphicsEndImageContext()
        return
    }

    UIGraphicsEndImageContext()

    blendingImageView.image = blendedImage
}

func updateBlendingView2() {

    guard let mainViewImage = getImageFromView(view: mainImageView) else {return}

    // Reset image to original state before blending
    blendingImageView.image = UIImage(named: "square")!
    guard let blendingImage = getImageFromView(view: blendingImageView) else {return}

    UIGraphicsBeginImageContextWithOptions(blendingImageView.bounds.size, true, UIScreen.main.scale)
    guard let context = UIGraphicsGetCurrentContext() else { UIGraphicsEndImageContext();return}

    let blendingOriginTransform = CGAffineTransform(translationX: (blendingImageView.bounds.width * 0.5), y: (blendingImageView.bounds.height * 0.5))

    let radians:Float = atan2f(Float(blendingImageView.transform.b), Float(blendingImageView.transform.a))
    let rotateTransform: CGAffineTransform = CGAffineTransform(rotationAngle: CGFloat(radians))

    context.concatenate(blendingOriginTransform)
    context.concatenate(rotateTransform.inverted())

    let mainViewOrigin = CGPoint(x: -blendingImageView.frame.origin.x - (blendingImageView.bounds.width * 0.5) , y: -blendingImageView.frame.origin.y - (blendingImageView.bounds.height * 0.5))
    mainViewImage.draw(at: mainViewOrigin)

    // undo the rotate
    context.concatenate(rotateTransform)

    let blendingDrawingOrigin = CGPoint(x: -blendingImageView.bounds.width * 0.5, y: -blendingImageView.bounds.height * 0.5)
    blendingImage.draw(at: blendingDrawingOrigin, blendMode: .multiply , alpha: 0.5)

    guard let blendedImage = UIGraphicsGetImageFromCurrentImageContext() else {
        UIGraphicsEndImageContext()
        return
    }

    UIGraphicsEndImageContext()
    blendingImageView.image = blendedImage
}

func getImageFromView(view: UIView) -> UIImage? {
    UIGraphicsBeginImageContextWithOptions(view.bounds.size, true, UIScreen.main.scale)
    guard let context = UIGraphicsGetCurrentContext() else { UIGraphicsEndImageContext();return nil}
    view.layer.render(in: context)
    let snapshotImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return snapshotImage
}

Any help would be appreciated, many thanks. You can find a small demo project to illustrate this question in this link https://github.com/Gregortega/BlendDemo

I have also looked into different tutorials on core graphics to understand the issue. Swift: Translating and Rotating a CGContext, A Visual Explanation (iOS/Xcode)

Upvotes: 0

Views: 1192

Answers (1)

Greg Ortega
Greg Ortega

Reputation: 11

I will answer my own question. A friend gave me a solution for the rotation issue.

The solution was to account for the difference or change in position of the blending imageview.

func updateBlendingView2(xDiff: CGFloat = 0, yDiff: CGFloat = 0) {

    guard let mainViewImage = getImageFromView(view: mainImageView) else {return}

    // Reset image to original state before blending
    blendingImageView.image = UIImage(named: "square")!
    guard let blendingImage = getImageFromView(view: blendingImageView) else {return}

    UIGraphicsBeginImageContextWithOptions(blendingImageView.bounds.size, true, UIScreen.main.scale)
    guard let context = UIGraphicsGetCurrentContext() else { UIGraphicsEndImageContext();return}

    // X, Y translation
    let blendingOriginTransform = CGAffineTransform(translationX: (blendingImageView.bounds.width * 0.5), y: (blendingImageView.bounds.height * 0.5))
    context.concatenate(blendingOriginTransform)

    // Rotation
    let radians:Float = atan2f(Float(blendingImageView.transform.b), Float(blendingImageView.transform.a))
    let rotateTransform: CGAffineTransform = CGAffineTransform(rotationAngle: CGFloat(radians))
    context.concatenate(rotateTransform.inverted())

    var mainViewOrigin = self.lastMainViewOrign
    mainViewOrigin = CGPoint(x: mainViewOrigin.x + xDiff, y: mainViewOrigin.y + yDiff)
    mainViewImage.draw(at: mainViewOrigin)
    self.lastMainViewOrign = mainViewOrigin

    // undo the rotate
    context.concatenate(rotateTransform)

    let blendingDrawingOrigin = CGPoint(x: -blendingImageView.bounds.width * 0.5, y: -blendingImageView.bounds.height * 0.5)
    blendingImage.draw(at: blendingDrawingOrigin, blendMode: .multiply , alpha: 0.5)

    guard let blendedImage = UIGraphicsGetImageFromCurrentImageContext() else {
        UIGraphicsEndImageContext()
        return
    }

    UIGraphicsEndImageContext()
    blendingImageView.image = blendedImage
}

Upvotes: 1

Related Questions