Search code examples
swiftuiimageview

After UIImage rotation white lines appear


I face a problem when after UIImage rotation the white lines appear on sides of the image. In my image redactor I have rotateButton. After tapping on it I call this function:

func rotateLeft() -> UIImage? {

    let radians = CGFloat.rotateRadians
    
    // Calculate the size of the rotated image
    var rotatedSize = CGRect(origin: .zero, size: size)
        .applying(CGAffineTransform(rotationAngle: radians))
        .integral.size

    // Round the dimensions to the nearest whole number
    rotatedSize.width = round(rotatedSize.width)
    rotatedSize.height = round(rotatedSize.height)

    // Create a context for the rotated image
    UIGraphicsBeginImageContext(rotatedSize)
    defer { UIGraphicsEndImageContext() }

    if let context = UIGraphicsGetCurrentContext() {
        // Move the origin to the center of the image
        context.translateBy(x: rotatedSize.width / 2.0, y: rotatedSize.height / 2.0)
        
        // Rotate the context
        context.rotate(by: radians)
        
        // Draw the image
        draw(in: CGRect(
            x: -size.width / 2.0,
            y: -size.height / 2.0,
            width: size.width,
            height: size.height)
        )
        
        // Get the rotated image from the context
        if let rotatedImage = UIGraphicsGetImageFromCurrentImageContext() {
            return rotatedImage
        }
    }
    
    return nil
}

This function return rotated image, after what I just animate image in UIImageVIew, and in completion I assigns this new rotate image to UIImageView (everything works fine, I think). But the problem is that if I spam this button many times (button tap only work when previous rotation logic finished), the white lines become bigger and bigger. enter image description here

I read that a problem is cause of floting points after rotation, so I tried to round size, but it doesn't help. So can someone explain what Is a problem, or give me some link where I can read about this problem.

P.S. When I use UIGraphicsImageRenderer it works much slower than previous deprecated method:

func rotateLeft() -> UIImage? {

    let radians = CGFloat.rotateRadians

    // Calculate the size of the rotated view's containing box for our drawing space

    let rotatedSize = CGSize(width: size.height, height: size.width)

    // Create the renderer with the rotatedSize
    let renderer = UIGraphicsImageRenderer(size: rotatedSize)

    // Perform the rotation transformation within the image renderer context
    let rotatedImage = renderer.image { context in
        context.cgContext.translateBy(x: rotatedSize.width / 2.0, y: rotatedSize.height / 2.0)
        context.cgContext.rotate(by: radians)

        // Translate and rotate the graphics context
        let origin = CGPoint(x: -size.width / 2, y: -size.height / 2)

        // Draw the original image in the rotated context
        draw(at: origin)
    }

    return rotatedImage
}

The problem with UIGraphicsImageRenderer was that it automatically take iPhone scale(in my case 3), so image 2000x1000 became 6000x3000. You need to setup scale factor to 1, in renderer.


Solution

  • The CGRect function applying(_ t: CGAffineTransform) -> CGRect doesn't return meaningful values for rotations unless they are multiples of 90°, and even then, you might get floating point errors. Does your app support arbitrary rotation angles? If not, I would suggest either leaving the rectangle the same size for 180° rotations, or just swapping the height and width for 90° rotations.

    That should preserve your image without introducing white bars on the sides, since you'll always be rendering your image into a rect with the exact number of pixels.