iOS 图片处理

小心使用UIGraphicsBeginImageContextWithOptions

Posted by Genie on April 19, 2019

前言

最近在写share,是通过UIActivityViewController,需求是根据API给的数据。一步一步添加二维码和waterText。需要不同的端share出去的图片是API 给的底图,只是在上面做添加文字水印,添加图片水印,很不小心出现bug,导致图片在不同的机器上面掉不起微信及QQ分享,最后不停的test,发现问题所在,我其实把原图渲染成3倍的图,image的scale变成3.0了,这也是造成share掉不起,因而收到如下error

1
[core] SLRemoteComposeViewController: (this may be harmless) viewServiceDidTerminateWithError: Error Domain=_UIViewServiceErrorDomain Code=1 "(null)" UserInfo={Terminated=disconnect method}

经过不断的测试

  1. scale 保持原图的,scale属性为1,不应该大于1
  2. 进过不断的测试图片大小不应该大于11M左右, 我测到大于11.3M 就不行了
  3. 一个图片本身就有问题

UIGraphicsBeginImageContext

我们创建一个基于位图的上下文(context),并将其设置为当前上下文(context)。

1
UIGraphicsBeginImageContext(size: CGSize)

但如果图片有特别的alpha和scale,会导致图片渲染出来不及UIGraphicsBeginImageContextWithOptions

UIGraphicsBeginImageContextWithOptions

1
UIGraphicsBeginImageContextWithOptions(imageSize, false, scale) 

size - 上下文大小 opaque - 指示位图是否不透明。如果您知道位图是完全不透明的,请指定true以忽略alpha通道并优化位图的存储。指定false意味着位图必须包含一个alpha通道来处理任何部分透明的像素。 scale - 一般我们会按照screen的scale 来设置,但是这里我的问题就于此产生,我需要保持原图大小,当然这里也不能设置为0.0,因为相等于UIScreen.main.scale

1
2
3
4
5
6
7
8
9
Parameters
size
The size (measured in points) of the new bitmap context. This represents the size of the image returned by the UIGraphicsGetImageFromCurrentImageContext() function. To get the size of the bitmap in pixels, you must multiply the width and height values by the value in the scale parameter.

opaque
A Boolean flag indicating whether the bitmap is opaque. If you know the bitmap is fully opaque, specify true to ignore the alpha channel and optimize the bitmap’s storage. Specifying false means that the bitmap must include an alpha channel to handle any partially transparent pixels.

scale
The scale factor to apply to the bitmap. If you specify a value of 0.0, the scale factor is set to the scale factor of the device’s main screen.

总结

总是有些坑,需要实践

code

图片和文本水印

extension UIImage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
enum WaterMarkCorner: String {
        case TopLeft = "up-left"
        case Center = "central"
    }

    /// create share image
    ///
    /// - Parameters:
    ///   - image: qrcode image
    ///   - qrCodeX: qrcode origin x
    ///   - qrCodeY: qrcode origin y
    /// - Returns: backgroundView + qrcode view synthetic
    func createShareImage(contextImage image: UIImage?, qrCodeX: CGFloat, qrCodeY: CGFloat) -> UIImage? {
        
        let sourceImage: UIImage? = self
        var imageSize: CGSize
        imageSize = self.size
        UIGraphicsBeginImageContextWithOptions(imageSize, false, 1.0)  //fixed bug ME-7102 Scale is set to 1.0 to keep the original image size and not render based on the screen
        sourceImage?.draw(at: CGPoint(x: 0, y: 0))
        
        let context = UIGraphicsGetCurrentContext()
        context?.drawPath(using: .stroke)
        
        guard let image2 = image else { return nil}
        let rect = CGRect(x: qrCodeX, y: qrCodeY, width: image2.size.width, height: image2.size.height)
        context?.addEllipse(in: rect)
        
        image2.draw(in: rect)
        
        let newImage: UIImage? = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return newImage
    }
    
    /// add waterMark with text
    ///
    /// - Parameters:
    ///   - waterMarkText: watermark text
    ///   - corner: location
    ///   - waterMarkTextColor: text color
    ///   - waterMarkTextFont: text font
    ///   - textX: text origin x
    ///   - textY: text origin y
    /// - Returns: synthetic view
    func waterMarkedImage( waterMarkText: String, corner: WaterMarkCorner, waterMarkTextColor: UIColor?, waterMarkTextFont: UIFont?, textX: CGFloat, textY: CGFloat) -> UIImage? {
        
        let textAttributes = [NSAttributedString.Key.font: waterMarkTextFont,
                              NSAttributedString.Key.foregroundColor: waterMarkTextColor]
        
        var textSize = waterMarkText.boundingRect(with: CGSize(width: CGFloat(MAXFLOAT),height: CGFloat(MAXFLOAT)), options: NSStringDrawingOptions.usesLineFragmentOrigin, attributes: textAttributes as [NSAttributedString.Key : Any], context: nil).size
        
        var textFrame = CGRect(x: 0, y: 0, width: textSize.width, height: textSize.height)
        
        var point: CGPoint?
        
        switch corner {
        case .TopLeft:
            point = CGPoint(x: textX, y: textY)
        case .Center:
            point = CGPoint(x: textX - textSize.width / 2, y: textY - textSize.height / 2)
        }
        textFrame = CGRect(origin: point ?? CGPoint.zero, size: textSize)
        
        let imageSize = self.size

        UIGraphicsBeginImageContextWithOptions(imageSize, false, 1.0)
        defer { UIGraphicsEndImageContext() }
        
        draw(in: CGRect(x: 0, y: 0, width: imageSize.width, height: imageSize.height))
        NSString(string: waterMarkText).draw(in: textFrame, withAttributes: textAttributes as [NSAttributedString.Key : Any])

        let waterMarkedImage: UIImage? = UIGraphicsGetImageFromCurrentImageContext()
        return waterMarkedImage
    }

生成二维码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class GenerateQrCodeUtils {

    class func setupQRCodeImage(text: String , size: CGSize) -> UIImage {
        
        let filter = CIFilter(name: "CIQRCodeGenerator", parameters: ["inputMessage" : text.data(using: String.Encoding.utf8)!])
        filter?.setDefaults()
        
        //Take out the generated qr code
        if let outputImage = filter?.outputImage {
            //Generate a better definition of the qr code
            let qrCodeImage = self.setupHighDefinitionUIImage(outputImage, size: size)
            return qrCodeImage
        }
        
        return UIImage()
    }
    
    // MARK: - Generate a high-definition UIImage
    private static func setupHighDefinitionUIImage(_ image: CIImage, size: CGSize) -> UIImage {
        let integral: CGRect = image.extent.integral
        let proportion: CGFloat = min(size.width/integral.width, size.height/integral.height)
        
        let width = integral.width * proportion
        let height = integral.height * proportion
        let colorSpace: CGColorSpace = CGColorSpaceCreateDeviceGray()
        let bitmapRef = CGContext(data: nil, width: Int(width), height: Int(height), bitsPerComponent: 8, bytesPerRow: 0, space: colorSpace, bitmapInfo: 0)!
        
        let context = CIContext(options: nil)
        let bitmapImage: CGImage = context.createCGImage(image, from: integral)!
        
        bitmapRef.interpolationQuality = CGInterpolationQuality.none
        bitmapRef.scaleBy(x: proportion, y: proportion)
        bitmapRef.draw(bitmapImage, in: integral)
        let image: CGImage = bitmapRef.makeImage()!
        return UIImage(cgImage: image)
    }

}

有问题可以联系Email