Home telegram - transform image node
Post
Cancel

telegram - transform image node

internalMediaGridMessageVideo

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
public func internalMediaGridMessageVideo(postbox: Postbox, videoReference: FileMediaReference, imageReference: ImageMediaReference? = nil, onlyFullSize: Bool = false, synchronousLoad: Bool = false, autoFetchFullSizeThumbnail: Bool = false) -> Signal<(() -> CGSize?, (TransformImageArguments) -> DrawingContext?), NoError> {
    let signal: Signal<Tuple3<Data?, Tuple2<Data, String>?, Bool>, NoError>
    if let imageReference = imageReference {
        signal = chatMessagePhotoDatas(postbox: postbox, photoReference: imageReference, tryAdditionalRepresentations: true, synchronousLoad: synchronousLoad)
        |> map { value -> Tuple3<Data?, Tuple2<Data, String>?, Bool> in
            let thumbnailData = value._0
            let fullSizeData = value._1
            let fullSizeComplete = value._2
            return Tuple(thumbnailData, fullSizeData.flatMap({ Tuple($0, "") }), fullSizeComplete)
        }
    } else {
        signal = chatMessageVideoDatas(postbox: postbox, fileReference: videoReference, onlyFullSize: onlyFullSize, synchronousLoad: synchronousLoad, autoFetchFullSizeThumbnail: autoFetchFullSizeThumbnail)
    }
    
    return signal
    |> map { value in
        let thumbnailData = value._0
        let fullSizeData = value._1
        let fullSizeComplete = value._2
        return ({
            var fullSizeImage: CGImage?
            if let fullSizeData = fullSizeData {
                if fullSizeComplete {
                    let options = NSMutableDictionary()
                    if let imageSource = CGImageSourceCreateWithData(fullSizeData._0 as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionary) {
                        fullSizeImage = image
                    }
                }
            }
            if let fullSizeImage = fullSizeImage {
                return CGSize(width: CGFloat(fullSizeImage.width), height: CGFloat(fullSizeImage.height))
            }
            return nil
        }, { arguments in
            let context = DrawingContext(size: arguments.drawingSize, clear: true)
            
            let drawingRect = arguments.drawingRect
            var drawingSize: CGSize
            if case .aspectFill = arguments.resizeMode {
                drawingSize = arguments.imageSize.aspectFilled(arguments.boundingSize)
            } else {
                drawingSize = arguments.imageSize.aspectFilled(arguments.boundingSize).fitted(arguments.imageSize)
            }
            if drawingSize.width < drawingRect.size.width && drawingSize.width >= drawingRect.size.width - 2.0 {
                drawingSize.width = drawingRect.size.width
            }
            if drawingSize.height < drawingRect.size.height && drawingSize.height >= drawingRect.size.height - 2.0 {
                drawingSize.height = drawingRect.size.height
            }
            let fittedRect = CGRect(origin: CGPoint(x: drawingRect.origin.x + (drawingRect.size.width - drawingSize.width) / 2.0, y: drawingRect.origin.y + (drawingRect.size.height - drawingSize.height) / 2.0), size: drawingSize)
            
            var fullSizeImage: CGImage?
            var imageOrientation: UIImage.Orientation = .up
            if let fullSizeData = fullSizeData {
                if fullSizeComplete {
                    let options = NSMutableDictionary()
                    if let imageSource = CGImageSourceCreateWithData(fullSizeData._0 as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionary) {
                        imageOrientation = imageOrientationFromSource(imageSource)
                        fullSizeImage = image
                    }
                } else {
                    let imageSource = CGImageSourceCreateIncremental(nil)
                    CGImageSourceUpdateData(imageSource, fullSizeData._0 as CFData, fullSizeComplete)
                    
                    let options = NSMutableDictionary()
                    options[kCGImageSourceShouldCache as NSString] = false as NSNumber
                    if let image = CGImageSourceCreateImageAtIndex(imageSource, 0, options as CFDictionary) {
                        imageOrientation = imageOrientationFromSource(imageSource)
                        fullSizeImage = image
                    }
                }
            }
            
            var thumbnailImage: CGImage?
            if let thumbnailData = thumbnailData, let imageSource = CGImageSourceCreateWithData(thumbnailData as CFData, nil), let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) {
                thumbnailImage = image
            }
            
            var blurredThumbnailImage: UIImage?
            if let thumbnailImage = thumbnailImage {
                if max(thumbnailImage.width, thumbnailImage.height) > Int(min(200.0, min(drawingSize.width, drawingSize.height))) {
                    blurredThumbnailImage = UIImage(cgImage: thumbnailImage)
                } else {
                    let thumbnailSize = CGSize(width: thumbnailImage.width, height: thumbnailImage.height)
                    let initialThumbnailContextFittingSize = drawingSize.fitted(CGSize(width: 90.0, height: 90.0))
                    
                    let thumbnailContextSize = thumbnailSize.aspectFitted(initialThumbnailContextFittingSize)
                    let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0)
                    thumbnailContext.withFlippedContext { c in
                        c.draw(thumbnailImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize))
                    }
                    uchatFastBlurMore(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes)
                    
                    var thumbnailContextFittingSize = CGSize(width: floor(arguments.drawingSize.width * 0.5), height: floor(arguments.drawingSize.width * 0.5))
                    if thumbnailContextFittingSize.width < 150.0 || thumbnailContextFittingSize.height < 150.0 {
                        thumbnailContextFittingSize = thumbnailContextFittingSize.aspectFilled(CGSize(width: 150.0, height: 150.0))
                    }
                    
                    if thumbnailContextFittingSize.width > thumbnailContextSize.width {
                        let additionalContextSize = thumbnailContextFittingSize
                        let additionalBlurContext = DrawingContext(size: additionalContextSize, scale: 1.0)
                        additionalBlurContext.withFlippedContext { c in
                            c.interpolationQuality = .default
                            if let image = thumbnailContext.generateImage()?.cgImage {
                                c.draw(image, in: CGRect(origin: CGPoint(), size: additionalContextSize))
                            }
                        }
                        imageFastBlur(Int32(additionalContextSize.width), Int32(additionalContextSize.height), Int32(additionalBlurContext.bytesPerRow), additionalBlurContext.bytes)
                        blurredThumbnailImage = additionalBlurContext.generateImage()
                    } else {
                        blurredThumbnailImage = thumbnailContext.generateImage()
                    }
                }
            }
            
            context.withFlippedContext { c in
                c.setBlendMode(.copy)
                if arguments.boundingSize != arguments.imageSize {
                    switch arguments.resizeMode {
                        case .blurBackground:
                            let blurSourceImage = thumbnailImage ?? fullSizeImage
                            
                            if let fullSizeImage = blurSourceImage {
                                var sideBlurredImage: UIImage?
                                let thumbnailSize = CGSize(width: fullSizeImage.width, height: fullSizeImage.height)
                                let initialThumbnailContextFittingSize = drawingSize.fitted(CGSize(width: 100.0, height: 100.0))
                                
                                let thumbnailContextSize = thumbnailSize.aspectFitted(initialThumbnailContextFittingSize)
                                let thumbnailContext = DrawingContext(size: thumbnailContextSize, scale: 1.0)
                                thumbnailContext.withFlippedContext { c in
                                    c.interpolationQuality = .none
                                    c.draw(fullSizeImage, in: CGRect(origin: CGPoint(), size: thumbnailContextSize))
                                }
                                imageFastBlur(Int32(thumbnailContextSize.width), Int32(thumbnailContextSize.height), Int32(thumbnailContext.bytesPerRow), thumbnailContext.bytes)
                                
                                var thumbnailContextFittingSize = CGSize(width: floor(arguments.drawingSize.width * 0.5), height: floor(arguments.drawingSize.width * 0.5))
                                if thumbnailContextFittingSize.width < 150.0 || thumbnailContextFittingSize.height < 150.0 {
                                    thumbnailContextFittingSize = thumbnailContextFittingSize.aspectFilled(CGSize(width: 150.0, height: 150.0))
                                }
                                
                                if thumbnailContextFittingSize.width > thumbnailContextSize.width {
                                    let additionalContextSize = thumbnailContextFittingSize
                                    let additionalBlurContext = DrawingContext(size: additionalContextSize, scale: 1.0)
                                    additionalBlurContext.withFlippedContext { c in
                                        c.interpolationQuality = .default
                                        if let image = thumbnailContext.generateImage()?.cgImage {
                                            c.draw(image, in: CGRect(origin: CGPoint(), size: additionalContextSize))
                                        }
                                    }
                                    imageFastBlur(Int32(additionalContextSize.width), Int32(additionalContextSize.height), Int32(additionalBlurContext.bytesPerRow), additionalBlurContext.bytes)
                                    sideBlurredImage = additionalBlurContext.generateImage()
                                } else {
                                    sideBlurredImage = thumbnailContext.generateImage()
                                }
                                
                                if let blurredImage = sideBlurredImage {
                                    let filledSize = thumbnailSize.aspectFilled(arguments.drawingRect.size)
                                    c.interpolationQuality = .medium
                                    c.draw(blurredImage.cgImage!, in: CGRect(origin: CGPoint(x: arguments.drawingRect.minX + (arguments.drawingRect.width - filledSize.width) / 2.0, y: arguments.drawingRect.minY + (arguments.drawingRect.height - filledSize.height) / 2.0), size: filledSize))
                                    c.setBlendMode(.normal)
                                    c.setFillColor((arguments.emptyColor ?? UIColor.white).withAlphaComponent(0.05).cgColor)
                                    c.fill(arguments.drawingRect)
                                    c.setBlendMode(.copy)
                                }
                            } else {
                                c.fill(arguments.drawingRect)
                            }
                        case let .fill(color):
                            c.setFillColor((arguments.emptyColor ?? color).cgColor)
                            c.fill(arguments.drawingRect)
                        case .aspectFill:
                            break
                    }
                }
                
                c.setBlendMode(.copy)
                
                if blurredThumbnailImage == nil, fullSizeImage == nil, let emptyColor = arguments.emptyColor {
                    c.setFillColor(emptyColor.cgColor)
                    c.fill(arguments.drawingRect)
                }
                
                if let blurredThumbnailImage = blurredThumbnailImage, let cgImage = blurredThumbnailImage.cgImage {
                    c.interpolationQuality = .default
                    drawImage(context: c, image: cgImage, orientation: imageOrientation, in: fittedRect)
                    c.setBlendMode(.normal)
                }
                
                if let fullSizeImage = fullSizeImage {
                    c.interpolationQuality = .medium
                    drawImage(context: c, image: fullSizeImage, orientation: imageOrientation, in: fittedRect)
                }
            }
            
            addCorners(context, arguments: arguments)
            
            return context
        })
    }
}

chatMessagePhotoDatas

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
public func chatMessagePhotoDatas(postbox: Postbox, photoReference: ImageMediaReference, fullRepresentationSize: CGSize = CGSize(width: 1280.0, height: 1280.0), autoFetchFullSize: Bool = false, tryAdditionalRepresentations: Bool = false, synchronousLoad: Bool = false) -> Signal<Tuple3<Data?, Data?, Bool>, NoError> {
    if let smallestRepresentation = smallestImageRepresentation(photoReference.media.representations), let largestRepresentation = photoReference.media.representationForDisplayAtSize(fullRepresentationSize) {
        let maybeFullSize = postbox.mediaBox.resourceData(largestRepresentation.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad)
        
        let signal = maybeFullSize
        |> take(1)
        |> mapToSignal { maybeData -> Signal<Tuple3<Data?, Data?, Bool>, NoError> in
            if maybeData.complete {
                let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: [])
                return .single(Tuple(nil, loadedData, true))
            } else {
                let decodedThumbnailData = photoReference.media.immediateThumbnailData.flatMap(decodeTinyThumbnail)
                let fetchedThumbnail: Signal<FetchResourceSourceType, FetchResourceError>
                if let _ = decodedThumbnailData {
                    fetchedThumbnail = .complete()
                } else {
                    fetchedThumbnail = fetchedMediaResource(mediaBox: postbox.mediaBox, reference: photoReference.resourceReference(smallestRepresentation.resource), statsCategory: .image)
                }
                let fetchedFullSize = fetchedMediaResource(mediaBox: postbox.mediaBox, reference: photoReference.resourceReference(largestRepresentation.resource), statsCategory: .image)
                
                let anyThumbnail: [Signal<MediaResourceData, NoError>]
                if tryAdditionalRepresentations {
                    anyThumbnail = photoReference.media.representations.filter({ representation in
                        return representation != largestRepresentation
                    }).map({ representation -> Signal<MediaResourceData, NoError> in
                        return postbox.mediaBox.resourceData(representation.resource)
                        |> take(1)
                    })
                } else {
                    anyThumbnail = []
                }
                
                let mainThumbnail = Signal<Data?, NoError> { subscriber in
                    if let decodedThumbnailData = decodedThumbnailData {
                        subscriber.putNext(decodedThumbnailData)
                        subscriber.putCompletion()
                        return EmptyDisposable
                    } else {
                        let fetchedDisposable = fetchedThumbnail.start()
                        let thumbnailDisposable = postbox.mediaBox.resourceData(smallestRepresentation.resource, attemptSynchronously: synchronousLoad).start(next: { next in
                            subscriber.putNext(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []))
                        }, error: subscriber.putError, completed: subscriber.putCompletion)
                        
                        return ActionDisposable {
                            fetchedDisposable.dispose()
                            thumbnailDisposable.dispose()
                        }
                    }
                }
                
                let thumbnail = combineLatest(anyThumbnail)
                |> mapToSignal { thumbnails -> Signal<Data?, NoError> in
                    for thumbnail in thumbnails {
                        if thumbnail.size != 0, let data = try? Data(contentsOf: URL(fileURLWithPath: thumbnail.path), options: []) {
                            return .single(data)
                        }
                    }
                    return mainThumbnail
                }
                
                let fullSizeData: Signal<Tuple2<Data?, Bool>, NoError>
                
                if autoFetchFullSize {
                    fullSizeData = Signal<Tuple2<Data?, Bool>, NoError> { subscriber in
                        let fetchedFullSizeDisposable = fetchedFullSize.start()
                        let fullSizeDisposable = postbox.mediaBox.resourceData(largestRepresentation.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad).start(next: { next in
                            subscriber.putNext(Tuple(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete))
                        }, error: subscriber.putError, completed: subscriber.putCompletion)
                        
                        return ActionDisposable {
                            fetchedFullSizeDisposable.dispose()
                            fullSizeDisposable.dispose()
                        }
                    }
                } else {
                    fullSizeData = postbox.mediaBox.resourceData(largestRepresentation.resource, option: .complete(waitUntilFetchStatus: false), attemptSynchronously: synchronousLoad)
                    |> map { next -> Tuple2<Data?, Bool> in
                        return Tuple(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: []), next.complete)
                    }
                }
                
                return thumbnail
                |> mapToSignal { thumbnailData in
                    if let thumbnailData = thumbnailData {
                        return fullSizeData
                        |> map { value in
                            return Tuple(thumbnailData, value._0, value._1)
                        }
                    } else {
                        return .single(Tuple(thumbnailData, nil, false))
                    }
                }
            }
        }
        |> distinctUntilChanged(isEqual: { lhs, rhs in
            if (lhs._0 == nil && lhs._1 == nil) && (rhs._0 == nil && rhs._1 == nil) {
                return true
            } else {
                return false
            }
        })
        
        return signal
    } else {
        return .never()
    }
}

decodeTinyThumbnail

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
public func decodeTinyThumbnail(data: Data) -> Data? {
    if data.count < 3 {
        return nil
    }
    guard let tinyThumbnailHeaderPattern = tinyThumbnailHeaderPattern, let tinyThumbnailFooterPattern = tinyThumbnailFooterPattern else {
        return nil
    }
    var version: UInt8 = 0
    data.copyBytes(to: &version, count: 1)
    if version != 1 {
        return nil
    }
    var width: UInt8 = 0
    var height: UInt8 = 0
    data.copyBytes(to: &width, from: 1 ..< 2)
    data.copyBytes(to: &height, from: 2 ..< 3)
    
    var resultData = Data()
    resultData.append(tinyThumbnailHeaderPattern)
    resultData.append(data.subdata(in: 3 ..< data.count))
    resultData.append(tinyThumbnailFooterPattern)
    resultData.withUnsafeMutableBytes({ (resultBytes: UnsafeMutablePointer<UInt8>) -> Void in
        resultBytes[164] = width
        resultBytes[166] = height
    })
    return resultData
}

tinyThumbnailHeaderPattern

1
private let tinyThumbnailHeaderPattern = Data(base64Encoded: "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDACgcHiMeGSgjISMtKygwPGRBPDc3PHtYXUlkkYCZlo+AjIqgtObDoKrarYqMyP/L2u71////m8H////6/+b9//j/2wBDASstLTw1PHZBQXb4pYyl+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj4+Pj/wAARCAAAAAADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwA=")
1
private let tinyThumbnailFooterPattern = Data(base64Encoded: "/9k=")

UIKitUtils

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
func fitted(_ size: CGSize) -> CGSize {
    var fittedSize = self
    if fittedSize.width > size.width {
        fittedSize = CGSize(width: size.width, height: floor((fittedSize.height * size.width / max(fittedSize.width, 1.0))))
    }
    if fittedSize.height > size.height {
        fittedSize = CGSize(width: floor((fittedSize.width * size.height / max(fittedSize.height, 1.0))), height: size.height)
    }
    return fittedSize
}
func cropped(_ size: CGSize) -> CGSize {
    return CGSize(width: min(size.width, self.width), height: min(size.height, self.height))
}
func fittedToArea(_ area: CGFloat) -> CGSize {
    if self.height < 1.0 || self.width < 1.0 {
        return CGSize()
    }
    let aspect = self.width / self.height
    let height = sqrt(area / aspect)
    let width = aspect * height
    return CGSize(width: floor(width), height: floor(height))
}

func aspectFilled(_ size: CGSize) -> CGSize {
    let scale = max(size.width / max(1.0, self.width), size.height / max(1.0, self.height))
    return CGSize(width: floor(self.width * scale), height: floor(self.height * scale))
}
func aspectFitted(_ size: CGSize) -> CGSize {
    let scale = min(size.width / max(1.0, self.width), size.height / max(1.0, self.height))
    return CGSize(width: floor(self.width * scale), height: floor(self.height * scale))
}
func aspectFittedOrSmaller(_ size: CGSize) -> CGSize {
    let scale = min(1.0, min(size.width / max(1.0, self.width), size.height / max(1.0, self.
    return CGSize(width: floor(self.width * scale), height: floor(self.height * scale))
}
func aspectFittedWithOverflow(_ size: CGSize, leeway: CGFloat) -> CGSize {
    let scale = min(size.width / max(1.0, self.width), size.height / max(1.0, self.height))
    var result = CGSize(width: floor(self.width * scale), height: floor(self.height * scale)
    if result.width < size.width && result.width > size.width - leeway {
        result.height += size.width - result.width
        result.width = size.width
    }
    if result.height < size.height && result.height > size.height - leeway {
        result.width += size.height - result.height
        result.height = size.height
    }
    return result
}
  
func fittedToWidthOrSmaller(_ width: CGFloat) -> CGSize {
    let scale = min(1.0, width / max(1.0, self.width))
    return CGSize(width: floor(self.width * scale), height: floor(self.height * scale))
}
func multipliedByScreenScale() -> CGSize {
    let scale = UIScreenScale
    return CGSize(width: self.width * scale, height: self.height * scale)
}
func dividedByScreenScale() -> CGSize {
    let scale = UIScreenScale
    return CGSize(width: self.width / scale, height: self.height / scale)
}
var integralFloor: CGSize {
    return CGSize(width: floor(self.width), height: floor(self.height))
}  

uchatFastBlurMore

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
void uchatFastBlurMore(int imageWidth, int imageHeight, int imageStride, void * _Nonnull pixels)
{
    uint8_t *pix = (uint8_t *)pixels;
    const int w = imageWidth;
    const int h = imageHeight;
    const int stride = imageStride;
    const int radius = 7;
    const int r1 = radius + 1;
    const int div = radius * 2 + 1;
    
    if (radius > 15 || div >= w || div >= h)
    {
        return;
    }
    
    uint64_t *rgb = malloc(imageStride * imageHeight * sizeof(uint64_t));
    
    int x, y, i;
    
    int yw = 0;
    const int we = w - r1;
    for (y = 0; y < h; y++) {
        uint64_t cur = get_colors (&pix[yw]);
        uint64_t rgballsum = -radius * cur;
        uint64_t rgbsum = cur * ((r1 * (r1 + 1)) >> 1);
        
        for (i = 1; i <= radius; i++) {
            uint64_t cur = get_colors (&pix[yw + i * 4]);
            rgbsum += cur * (r1 - i);
            rgballsum += cur;
        }
        
        x = 0;
        
#define update(start, middle, end)                         \
rgb[y * w + x] = (rgbsum >> 6) & 0x00FF00FF00FF00FF; \
\
rgballsum += get_colors (&pix[yw + (start) * 4]) -   \
2 * get_colors (&pix[yw + (middle) * 4]) +  \
get_colors (&pix[yw + (end) * 4]);      \
rgbsum += rgballsum;                                 \
x++;                                                 \

        while (x < r1) {
            update (0, x, x + r1);
        }
        while (x < we) {
            update (x - r1, x, x + r1);
        }
        while (x < w) {
            update (x - r1, x, w - 1);
        }
#undef update
        
        yw += stride;
    }
    
    const int he = h - r1;
    for (x = 0; x < w; x++) {
        uint64_t rgballsum = -radius * rgb[x];
        uint64_t rgbsum = rgb[x] * ((r1 * (r1 + 1)) >> 1);
        for (i = 1; i <= radius; i++) {
            rgbsum += rgb[i * w + x] * (r1 - i);
            rgballsum += rgb[i * w + x];
        }
        
        y = 0;
        int yi = x * 4;
        
#define update(start, middle, end)         \
int64_t res = rgbsum >> 6;           \
pix[yi] = (uint8_t)res;                       \
pix[yi + 1] = (uint8_t)(res >> 16);             \
pix[yi + 2] = (uint8_t)(res >> 32);             \
\
rgballsum += rgb[x + (start) * w] -  \
2 * rgb[x + (middle) * w] + \
rgb[x + (end) * w];     \
rgbsum += rgballsum;                 \
y++;                                 \
yi += stride;
        
        while (y < r1) {
            update (0, y, y + r1);
        }
        while (y < he) {
            update (y - r1, y, y + r1);
        }
        while (y < h) {
            update (y - r1, y, h - 1);
        }
#undef update
    }
    
    free(rgb);
}

imageFastBlur

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
void imageFastBlur(int imageWidth, int imageHeight, int imageStride, void * _Nonnull pixels)
{
    uint8_t *pix = (uint8_t *)pixels;
    const int w = imageWidth;
    const int h = imageHeight;
    const int stride = imageStride;
    const int radius = 3;
    const int r1 = radius + 1;
    const int div = radius * 2 + 1;
    
    if (radius > 15 || div >= w || div >= h)
    {
        return;
    }
    
    uint64_t *rgb = malloc(imageStride * imageHeight * sizeof(uint64_t));
    
    int x, y, i;
    
    int yw = 0;
    const int we = w - r1;
    for (y = 0; y < h; y++) {
        uint64_t cur = get_colors (&pix[yw]);
        uint64_t rgballsum = -radius * cur;
        uint64_t rgbsum = cur * ((r1 * (r1 + 1)) >> 1);
        
        for (i = 1; i <= radius; i++) {
            uint64_t cur = get_colors (&pix[yw + i * 4]);
            rgbsum += cur * (r1 - i);
            rgballsum += cur;
        }
        
        x = 0;
        
#define update(start, middle, end)                         \
rgb[y * w + x] = (rgbsum >> 4) & 0x00FF00FF00FF00FF; \
\
rgballsum += get_colors (&pix[yw + (start) * 4]) -   \
2 * get_colors (&pix[yw + (middle) * 4]) +  \
get_colors (&pix[yw + (end) * 4]);      \
rgbsum += rgballsum;                                 \
x++;                                                 \

        while (x < r1) {
            update (0, x, x + r1);
        }
        while (x < we) {
            update (x - r1, x, x + r1);
        }
        while (x < w) {
            update (x - r1, x, w - 1);
        }
#undef update
        
        yw += stride;
    }
    
    const int he = h - r1;
    for (x = 0; x < w; x++) {
        uint64_t rgballsum = -radius * rgb[x];
        uint64_t rgbsum = rgb[x] * ((r1 * (r1 + 1)) >> 1);
        for (i = 1; i <= radius; i++) {
            rgbsum += rgb[i * w + x] * (r1 - i);
            rgballsum += rgb[i * w + x];
        }
        
        y = 0;
        int yi = x * 4;
        
#define update(start, middle, end)         \
int64_t res = rgbsum >> 4;           \
pix[yi] = (uint8_t)res;                       \
pix[yi + 1] = (uint8_t)(res >> 16);             \
pix[yi + 2] = (uint8_t)(res >> 32);             \
\
rgballsum += rgb[x + (start) * w] -  \
2 * rgb[x + (middle) * w] + \
rgb[x + (end) * w];     \
rgbsum += rgballsum;                 \
y++;                                 \
yi += stride;
        
        while (y < r1) {
            update (0, y, y + r1);
        }
        while (y < he) {
            update (y - r1, y, y + r1);
        }
        while (y < h) {
            update (y - r1, y, h - 1);
        }
#undef update
    }
    
    free(rgb);
}

get_colors

1
2
3
static inline uint64_t get_colors (const uint8_t *p) {
    return p[0] + (p[1] << 16) + ((uint64_t)p[2] << 32);
}

stickerThumbnailAlphaBlur

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void stickerThumbnailAlphaBlur(int imageWidth, int imageHeight, int imageStride, void * _Nonnull pixels) {
    vImage_Buffer srcBuffer;
    srcBuffer.width = imageWidth;
    srcBuffer.height = imageHeight;
    srcBuffer.rowBytes = imageStride;
    srcBuffer.data = pixels;
    
    {
        vImage_Buffer dstBuffer;
        dstBuffer.width = imageWidth;
        dstBuffer.height = imageHeight;
        dstBuffer.rowBytes = imageStride;
        dstBuffer.data = pixels;
        
        int boxSize = 2;
        boxSize = boxSize - (boxSize % 2) + 1;
        
        vImageBoxConvolve_ARGB8888(&srcBuffer, &dstBuffer, NULL, 0, 0, boxSize, boxSize, NULL, kvImageEdgeExtend);
    }
}

modifyImage

1
2
3
4
5
6
7
8
9
10
11
static void modifyImage(void *pixels, unsigned int width, unsigned int height, unsigned int stride, int16_t * _Nonnull matrix)
{
    vImage_Buffer dstBuffer;
    dstBuffer.width = width;
    dstBuffer.height = height;
    dstBuffer.rowBytes = stride;
    dstBuffer.data = pixels;
    
    int32_t divisor = 256;
    vImageMatrixMultiply_ARGB8888(&dstBuffer, &dstBuffer, matrix, divisor, NULL, NULL, kvImageDoNotTile);
}

matrixMul

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void matrixMul(CGFloat * _Nonnull a, CGFloat * _Nonnull b, CGFloat *result)
{
    for (int i = 0; i != 4; ++i)
    {
        for (int j = 0; j != 4; ++j)
        {
            CGFloat sum = 0;
            for (int k = 0; k != 4; ++k)
            {
                sum += a[i + k * 4] * b[k + j * 4];
            }
            result[i + j * 4] = sum;
        }
    }
}

lightBrightenMatrix

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
static int16_t *lightBrightenMatrix(int32_t * _Nullable outDivisor)
{
    static int16_t saturationMatrix[16];
    static const int32_t divisor = 256;
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^
    {
        CGFloat s = 1.2f;
        CGFloat offset = 0.01f;
        CGFloat factor = 1.02f;
        CGFloat satMatrix[] = {
          0.0722f + 0.9278f * s,  0.0722f - 0.0722f * s,  0.0722f - 0.0722f * s,  0,
          0.7152f - 0.7152f * s,  0.7152f + 0.2848f * s,  0.7152f - 0.7152f * s,  0,
          0.2126f - 0.2126f * s,  0.2126f - 0.2126f * s,  0.2126f + 0.7873f * s,  0,
          0.0f,                    0.0f,                    0.0f,  1,
        };
        CGFloat contrastMatrix[] = {
          factor, 0.0f, 0.0f, 0.0f,
          0.0f, factor, 0.0f, 0.0f,
          0.0f, 0.0f, factor, 0.0f,
          offset, offset, offset, 1.0f
        };
        CGFloat colorMatrix[16];
        matrixMul(satMatrix, contrastMatrix, colorMatrix);

        NSUInteger matrixSize = sizeof(colorMatrix) / sizeof(colorMatrix[0]);
        for (NSUInteger i = 0; i < matrixSize; ++i) {
            saturationMatrix[i] = (int16_t)round(colorMatrix[i] * divisor);
        }
    });
    
    if (outDivisor != NULL)
        *outDivisor = divisor;
    
    return saturationMatrix;
}

uchatBrightenImage

1
2
3
4
void uchatBrightenImage(int imageWidth, int imageHeight, int imageStride, void * _Nonnull pixels)
{
    modifyImage(pixels, imageWidth, imageHeight, imageStride, lightBrightenMatrix(NULL));
}

addCorners

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
public func addCorners(_ context: DrawingContext, arguments: TransformImageArguments) {
    let corners = arguments.corners
    let drawingRect = arguments.drawingRect
    if case let .Corner(radius) = corners.topLeft, radius > CGFloat.ulpOfOne {
        let corner = cornerContext(.TopLeft(Int(radius)))
        context.blt(corner, at: CGPoint(x: drawingRect.minX, y: drawingRect.minY))
    }
    
    if case let .Corner(radius) = corners.topRight, radius > CGFloat.ulpOfOne {
        let corner = cornerContext(.TopRight(Int(radius)))
        context.blt(corner, at: CGPoint(x: drawingRect.maxX - radius, y: drawingRect.minY))
    }
    
    switch corners.bottomLeft {
        case let .Corner(radius):
            if radius > CGFloat.ulpOfOne {
                let corner = cornerContext(.BottomLeft(Int(radius)))
                context.blt(corner, at: CGPoint(x: drawingRect.minX, y: drawingRect.maxY - radius))
            }
        case let .Tail(radius, enabled):
            if radius > CGFloat.ulpOfOne {
                if enabled {
                    let tail = tailContext(.BottomLeft(Int(radius)))
                    let color = context.colorAt(CGPoint(x: drawingRect.minX, y: drawingRect.maxY - 1.0))
                    context.withContext { c in
                        c.clear(CGRect(x: drawingRect.minX - 3.0, y: 0.0, width: 3.0, height: drawingRect.maxY - 6.0))
                        c.setFillColor(color.cgColor)
                        c.fill(CGRect(x: 0.0, y: drawingRect.maxY - 6.0, width: 3.0, height: 6.0))
                    }
                    context.blt(tail, at: CGPoint(x: drawingRect.minX - 3.0, y: drawingRect.maxY - radius))
                } else {
                    let corner = cornerContext(.BottomLeft(Int(radius)))
                    context.blt(corner, at: CGPoint(x: drawingRect.minX, y: drawingRect.maxY - radius))
                }
            }
        
    }
    
    switch corners.bottomRight {
        case let .Corner(radius):
            if radius > CGFloat.ulpOfOne {
                let corner = cornerContext(.BottomRight(Int(radius)))
                context.blt(corner, at: CGPoint(x: drawingRect.maxX - radius, y: drawingRect.maxY - radius))
            }
        case let .Tail(radius, enabled):
            if radius > CGFloat.ulpOfOne {
                if enabled {
                    let tail = tailContext(.BottomRight(Int(radius)))
                    let color = context.colorAt(CGPoint(x: drawingRect.maxX - 1.0, y: drawingRect.maxY - 1.0))
                    context.withContext { c in
                        c.clear(CGRect(x: drawingRect.maxX, y: 0.0, width: 3.0, height: drawingRect.maxY - 6.0))
                        c.setFillColor(color.cgColor)
                        c.fill(CGRect(x: drawingRect.maxX, y: drawingRect.maxY - 6.0, width: 3.0, height: 6.0))
                    }
                    context.blt(tail, at: CGPoint(x: drawingRect.maxX - radius, y: drawingRect.maxY - radius))
                } else {
                    let corner = cornerContext(.BottomRight(Int(radius)))
                    context.blt(corner, at: CGPoint(x: drawingRect.maxX - radius, y: drawingRect.maxY - radius))
                }
            }
    }
}

ImageCorner

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
public enum ImageCorner: Equatable {
    case Corner(CGFloat)
    case Tail(CGFloat, Bool)
    
    public var extendedInsets: CGSize {
        switch self {
            case .Tail:
                return CGSize(width: 3.0, height: 0.0)
            default:
                return CGSize()
        }
    }
    
    public var withoutTail: ImageCorner {
        switch self {
            case .Corner:
                return self
            case let .Tail(radius, _):
                return .Corner(radius)
        }
    }
    
    public var radius: CGFloat {
        switch self {
            case let .Corner(radius):
                return radius
            case let .Tail(radius, _):
                return radius
        }
    }
    
    public func scaledBy(_ scale: CGFloat) -> ImageCorner {
        switch self {
            case let .Corner(radius):
                return .Corner(radius * scale)
            case let .Tail(radius, enabled):
                return .Tail(radius * scale, enabled)
        }
    }
}

TransformImageArguments

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
public struct TransformImageArguments: Equatable {
    public let corners: ImageCorners
    
    public let imageSize: CGSize
    public let boundingSize: CGSize
    public let intrinsicInsets: UIEdgeInsets
    public let resizeMode: TransformImageResizeMode
    public let emptyColor: UIColor?
    public let scale: CGFloat?
    
    public init(corners: ImageCorners, imageSize: CGSize, boundingSize: CGSize, intrinsicInsets: UIEdgeInsets, resizeMode: TransformImageResizeMode = .fill(.black), emptyColor: UIColor? = nil, scale: CGFloat? = nil) {
        self.corners = corners
        self.imageSize = imageSize
        self.boundingSize = boundingSize
        self.intrinsicInsets = intrinsicInsets
        self.resizeMode = resizeMode
        self.emptyColor = emptyColor
        self.scale = scale
    }
    
    public var drawingSize: CGSize {
        let cornersExtendedEdges = self.corners.extendedEdges
        return CGSize(width: self.boundingSize.width + cornersExtendedEdges.left + cornersExtendedEdges.right + self.intrinsicInsets.left + self.intrinsicInsets.right, height: self.boundingSize.height + cornersExtendedEdges.top + cornersExtendedEdges.bottom + self.intrinsicInsets.top + self.intrinsicInsets.bottom)
    }
    
    public var drawingRect: CGRect {
        let cornersExtendedEdges = self.corners.extendedEdges
        return CGRect(x: cornersExtendedEdges.left + self.intrinsicInsets.left, y: cornersExtendedEdges.top + self.intrinsicInsets.top, width: self.boundingSize.width, height: self.boundingSize.height);
    }
    
    public var insets: UIEdgeInsets {
        let cornersExtendedEdges = self.corners.extendedEdges
        return UIEdgeInsets(top: cornersExtendedEdges.top + self.intrinsicInsets.top, left: cornersExtendedEdges.left + self.intrinsicInsets.left, bottom: cornersExtendedEdges.bottom + self.intrinsicInsets.bottom, right: cornersExtendedEdges.right + self.intrinsicInsets.right)
    }
    
    public static func ==(lhs: TransformImageArguments, rhs: TransformImageArguments) -> Bool {
        return lhs.imageSize == rhs.imageSize && lhs.boundingSize == rhs.boundingSize && lhs.corners == rhs.corners && lhs.emptyColor == rhs.emptyColor
    }
}
This post is licensed under CC BY 4.0 by the author.