Skip to content

Instantly share code, notes, and snippets.

@hanrw
Last active November 14, 2024 13:22
Show Gist options
  • Save hanrw/20ce624888ebc1cceef75c1f19f518dd to your computer and use it in GitHub Desktop.
Save hanrw/20ce624888ebc1cceef75c1f19f518dd to your computer and use it in GitHub Desktop.
func mosaicRectangles(in image: UIImage, from quadrilaterals: [QuadrilateralBox]) -> [QuadrilateralBox: UIImage] {
var resultMap: [QuadrilateralBox: UIImage] = [:]
guard var ciImage = CIImage(image: image) else {
print("Couldn't create CIImage.")
return resultMap
}
// Correct the orientation because `CIImage.init` can lose it
ciImage = ciImage.oriented(forExifOrientation: Int32(image.imageOrientation.rawValue))
let ciContext = CIContext(options: nil)
for quad in quadrilaterals {
// Create a CGPath representing the quadrilateral
let path = CGMutablePath()
path.move(to: quad.topLeft.asCGPoint)
path.addLine(to: quad.topRight.asCGPoint)
path.addLine(to: quad.bottomRight.asCGPoint)
path.addLine(to: quad.bottomLeft.asCGPoint)
path.closeSubpath()
// Calculate the bounding box of the quadrilateral
let boundingBox = path.boundingBoxOfPath
// Create a mask image for the quadrilateral
UIGraphicsBeginImageContext(ciImage.extent.size)
if let context = UIGraphicsGetCurrentContext() {
context.addPath(path)
context.setFillColor(UIColor.white.cgColor)
context.fillPath()
}
guard let maskUIImage = UIGraphicsGetImageFromCurrentImageContext() else {
UIGraphicsEndImageContext()
continue
}
UIGraphicsEndImageContext()
guard let maskCIImage = CIImage(image: maskUIImage) else {
continue
}
// Apply the mask to only the relevant part of the image
let maskCropped = maskCIImage.cropped(to: boundingBox)
let ciImageCropped = ciImage.cropped(to: boundingBox)
let pixelationScale: CGFloat = 20.0
let pixelatedImage = ciImageCropped
.applyingFilter("CIPixellate", parameters: [kCIInputScaleKey: pixelationScale])
let resultImage = pixelatedImage.applyingFilter("CIBlendWithMask", parameters: [
kCIInputBackgroundImageKey: ciImageCropped,
kCIInputMaskImageKey: maskCropped
])
if let cgImage = ciContext.createCGImage(resultImage, from: boundingBox) {
let resultUIImage = UIImage(cgImage: cgImage)
resultMap[quad] = resultUIImage
}
}
return resultMap
}
@hanrw
Copy link
Author

hanrw commented Nov 14, 2024

private func cropQuadrilateralImage(from image: UIImage, using quad: QuadrilateralBox, in containerSize: CGSize) -> UIImage? {
        guard let ciImage = CIImage(image: image) else {
            print("Couldn't create CIImage.")
            return nil
        }

        let scaleWidth = ciImage.extent.width / containerSize.width
        let scaleHeight = ciImage.extent.height / containerSize.height

        let orientedImage = ciImage.oriented(forExifOrientation: Int32(image.imageOrientation.rawValue))

        let filteredImage = orientedImage.applyingFilter("CIPerspectiveCorrection", parameters: [
            "inputTopLeft": CIVector(x: quad.bottomLeft.x * scaleWidth, y: quad.bottomLeft.y * scaleHeight),
            "inputTopRight": CIVector(x: quad.bottomRight.x * scaleWidth, y: quad.bottomRight.y * scaleHeight),
            "inputBottomLeft": CIVector(x: quad.topLeft.x * scaleWidth, y: quad.topLeft.y * scaleHeight),
            "inputBottomRight": CIVector(x: quad.topRight.x * scaleWidth, y: quad.topRight.y * scaleHeight)
        ])

        return UIImage.from(ciImage: filteredImage)
    }

@hanrw
Copy link
Author

hanrw commented Nov 14, 2024

 func blurRectangles(in image: UIImage, from quadrilaterals: [QuadrilateralBox]) -> UIImage? {
        guard var ciImage = CIImage(image: image) else {
            print("Couldn't create CIImage.")
            return nil
        }

        // correct the orientation becuase `CIImage.init` can lose it
        ciImage = ciImage.oriented(forExifOrientation: Int32(image.imageOrientation.rawValue))

        let ciContext = CIContext(options: nil)
        var maskCanvasImage = CIImage.empty().cropped(to: ciImage.extent)

        for quad in quadrilaterals {
            // Create a CGPath representing the quadrilateral
            let path = CGMutablePath()
            path.move(to: quad.topLeft.asCGPoint)
            path.addLine(to: quad.topRight.asCGPoint)
            path.addLine(to: quad.bottomRight.asCGPoint)
            path.addLine(to: quad.bottomLeft.asCGPoint)
            path.closeSubpath()

            // Draw the path into a CIImage
            UIGraphicsBeginImageContext(ciImage.extent.size)
            if let context = UIGraphicsGetCurrentContext() {
                context.addPath(path)
                context.setFillColor(UIColor.white.cgColor)
                context.fillPath()
            }
            guard let maskUIImage = UIGraphicsGetImageFromCurrentImageContext() else {
                UIGraphicsEndImageContext()
                continue
            }
            UIGraphicsEndImageContext()

            guard let maskCIImage = CIImage(image: maskUIImage) else {
                continue
            }

            // Composite this mask over the existing canvas
            maskCanvasImage = maskCIImage.composited(over: maskCanvasImage)
        }

        let blurRadius: Double = 30.0
        let blurredImage = ciImage
                .clampedToExtent()
                .applyingGaussianBlur(sigma: blurRadius)
                .cropped(to: ciImage.extent)

        let resultImage = blurredImage.applyingFilter("CIBlendWithMask", parameters: [
            kCIInputBackgroundImageKey: ciImage,
            kCIInputMaskImageKey: maskCanvasImage
        ])

        if let cgImage = ciContext.createCGImage(resultImage, from: resultImage.extent) {
            return UIImage(cgImage: cgImage)
        }

        return nil
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment