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
}
}