Merge commit '7621e2f8dec938cf48181c8b10afc9b01f444e68' into beta

This commit is contained in:
Ilya Laktyushin
2025-12-06 02:17:48 +04:00
commit 8344b97e03
28070 changed files with 7995182 additions and 0 deletions
+31
View File
@@ -0,0 +1,31 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "AvatarNode",
module_name = "AvatarNode",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
"//submodules/Display:Display",
"//submodules/TelegramCore:TelegramCore",
"//submodules/Postbox",
"//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/AnimationUI:AnimationUI",
"//submodules/AppBundle:AppBundle",
"//submodules/AccountContext:AccountContext",
"//submodules/Emoji:Emoji",
"//submodules/TinyThumbnail:TinyThumbnail",
"//submodules/FastBlur:FastBlur",
"//submodules/ComponentFlow",
"//submodules/TelegramUI/Components/Stories/AvatarStoryIndicatorComponent",
"//submodules/DirectMediaImageCache",
],
visibility = [
"//visibility:public",
],
)
@@ -0,0 +1,337 @@
import Foundation
import UIKit
import Display
import Accelerate
public final class AvatarBadgeView: UIImageView {
enum OriginalContent: Equatable {
case color(UIColor)
case image(UIImage)
static func ==(lhs: OriginalContent, rhs: OriginalContent) -> Bool {
switch lhs {
case let .color(color):
if case .color(color) = rhs {
return true
} else {
return false
}
case let .image(lhsImage):
if case let .image(rhsImage) = rhs {
return lhsImage === rhsImage
} else {
return false
}
}
}
}
private struct Parameters: Equatable {
var size: CGSize
var text: String
var hasTimeoutIcon: Bool
var useSolidColor: Bool
var strokeColor: UIColor?
}
private var originalContent: OriginalContent?
private var parameters: Parameters?
private var hasContent: Bool = false
override public init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(content: OriginalContent) {
if self.originalContent != content || !self.hasContent {
self.originalContent = content
self.update()
}
}
public func update(size: CGSize, text: String, hasTimeoutIcon: Bool = true, useSolidColor: Bool = false, strokeColor: UIColor? = nil) {
let parameters = Parameters(size: size, text: text, hasTimeoutIcon: hasTimeoutIcon, useSolidColor: useSolidColor, strokeColor: strokeColor)
if self.parameters != parameters || !self.hasContent {
self.parameters = parameters
self.update()
}
}
private func update() {
guard let originalContent = self.originalContent, let parameters = self.parameters else {
return
}
self.hasContent = true
let blurredWidth = 16
let blurredHeight = 16
guard let blurredContext = DrawingContext(size: CGSize(width: CGFloat(blurredWidth), height: CGFloat(blurredHeight)), scale: 1.0, opaque: true) else {
return
}
let blurredSize = CGSize(width: CGFloat(blurredWidth), height: CGFloat(blurredHeight))
blurredContext.withContext { c in
switch originalContent {
case let .color(color):
c.setFillColor(color.cgColor)
c.fill(CGRect(origin: CGPoint(), size: blurredSize))
case let .image(image):
c.setFillColor(UIColor.black.cgColor)
c.fill(CGRect(origin: CGPoint(), size: blurredSize))
c.scaleBy(x: blurredSize.width / parameters.size.width, y: blurredSize.height / parameters.size.height)
let offsetFactor: CGFloat = 1.0 - 0.6
let imageFrame = CGRect(origin: CGPoint(x: parameters.size.width - image.size.width + offsetFactor * parameters.size.width, y: parameters.size.height - image.size.height + offsetFactor * parameters.size.height), size: image.size)
UIGraphicsPushContext(c)
image.draw(in: imageFrame)
UIGraphicsPopContext()
}
}
var rSum: Int64 = 0
var gSum: Int64 = 0
var bSum: Int64 = 0
for y in 0 ..< blurredHeight {
let row = blurredContext.bytes.assumingMemoryBound(to: UInt8.self).advanced(by: y * blurredContext.bytesPerRow)
for x in 0 ..< blurredWidth {
let pixel = row.advanced(by: x * 4)
bSum += Int64(pixel.advanced(by: 0).pointee)
gSum += Int64(pixel.advanced(by: 1).pointee)
rSum += Int64(pixel.advanced(by: 2).pointee)
}
}
let colorNorm = CGFloat(blurredWidth * blurredHeight)
let invColorNorm: CGFloat = 1.0 / (255.0 * colorNorm)
let aR = CGFloat(rSum) * invColorNorm
let aG = CGFloat(gSum) * invColorNorm
let aB = CGFloat(bSum) * invColorNorm
let luminance: CGFloat = 0.299 * aR + 0.587 * aG + 0.114 * aB
let isLightImage = luminance > 0.9
var brightness: CGFloat = 1.0
if isLightImage {
brightness = 0.99
} else {
brightness = 0.94
}
var destinationBuffer = vImage_Buffer()
destinationBuffer.width = UInt(blurredWidth)
destinationBuffer.height = UInt(blurredHeight)
destinationBuffer.data = blurredContext.bytes
destinationBuffer.rowBytes = blurredContext.bytesPerRow
vImageBoxConvolve_ARGB8888(
&destinationBuffer,
&destinationBuffer,
nil,
0, 0,
UInt32(15),
UInt32(15),
nil,
vImage_Flags(kvImageTruncateKernel | kvImageDoNotTile)
)
let divisor: Int32 = 0x1000
let rwgt: CGFloat = 0.3086
let gwgt: CGFloat = 0.6094
let bwgt: CGFloat = 0.0820
let adjustSaturation: CGFloat = 1.7
let a = (1.0 - adjustSaturation) * rwgt + adjustSaturation
let b = (1.0 - adjustSaturation) * rwgt
let c = (1.0 - adjustSaturation) * rwgt
let d = (1.0 - adjustSaturation) * gwgt
let e = (1.0 - adjustSaturation) * gwgt + adjustSaturation
let f = (1.0 - adjustSaturation) * gwgt
let g = (1.0 - adjustSaturation) * bwgt
let h = (1.0 - adjustSaturation) * bwgt
let i = (1.0 - adjustSaturation) * bwgt + adjustSaturation
let satMatrix: [CGFloat] = [
a, b, c, 0,
d, e, f, 0,
g, h, i, 0,
0, 0, 0, 1
]
let brighnessMatrix: [CGFloat] = [
brightness, 0, 0, 0,
0, brightness, 0, 0,
0, 0, brightness, 0,
0, 0, 0, 1
]
func matrixMul(a: [CGFloat], b: [CGFloat], result: inout [CGFloat]) {
for i in 0 ..< 4 {
for j in 0 ..< 4 {
var sum: CGFloat = 0.0
for k in 0 ..< 4 {
sum += a[i + k * 4] * b[k + j * 4]
}
result[i + j * 4] = sum
}
}
}
var resultMatrix = Array<CGFloat>(repeating: 0.0, count: 4 * 4)
matrixMul(a: satMatrix, b: brighnessMatrix, result: &resultMatrix)
var matrix: [Int16] = resultMatrix.map { value in
return Int16(value * CGFloat(divisor))
}
vImageMatrixMultiply_ARGB8888(&destinationBuffer, &destinationBuffer, &matrix, divisor, nil, nil, vImage_Flags(kvImageDoNotTile))
guard let blurredImage = blurredContext.generateImage() else {
return
}
var solidColor: UIColor?
if parameters.useSolidColor {
let context = DrawingContext(size: CGSize(width: 1.0, height: 1.0), scale: 1.0, clear: false)!
context.withFlippedContext({ context in
if let cgImage = blurredImage.cgImage {
context.draw(cgImage, in: CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0))
}
})
solidColor = context.colorAt(.zero)
}
var badgeSize = parameters.size
let strokeWidth: CGFloat = 1.0 + UIScreenPixel
var size = parameters.size
var offset: CGPoint = .zero
if parameters.strokeColor != nil {
offset = CGPoint(x: strokeWidth / 2.0, y: strokeWidth / 2.0)
badgeSize.width += strokeWidth
badgeSize.height += strokeWidth
size.width += strokeWidth * 2.0
size.height += strokeWidth * 2.0
}
self.image = generateImage(size, rotatedContext: { size, context in
UIGraphicsPushContext(context)
context.clear(CGRect(origin: CGPoint(), size: size))
let textColor: UIColor
if parameters.useSolidColor {
textColor = .white
} else {
if isLightImage {
textColor = UIColor(white: 0.7, alpha: 1.0)
} else {
textColor = .white
}
}
if var solidColor {
func adjustedBackgroundColor(backgroundColor: UIColor, textColor: UIColor) -> UIColor {
let minContrastRatio: CGFloat = 4.5
if backgroundColor.contrastRatio(with: textColor) < minContrastRatio {
var hue: CGFloat = 0
var saturation: CGFloat = 0
var brightness: CGFloat = 0
var alpha: CGFloat = 0
backgroundColor.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha)
if brightness > 0.7 {
brightness = brightness * 0.9
saturation = min(saturation + 0.1, 1)
}
return UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: alpha)
} else {
return backgroundColor
}
}
solidColor = adjustedBackgroundColor(backgroundColor: solidColor, textColor: textColor)
if let strokeColor = parameters.strokeColor {
context.setStrokeColor(strokeColor.cgColor)
context.setLineWidth(strokeWidth)
}
context.setFillColor(solidColor.cgColor)
} else {
context.setBlendMode(.copy)
context.setFillColor(UIColor.black.cgColor)
}
if badgeSize.width != badgeSize.height {
let path = UIBezierPath(roundedRect: CGRect(origin: offset, size: badgeSize), cornerRadius: badgeSize.height / 2.0)
context.addPath(path.cgPath)
if let _ = parameters.strokeColor {
context.drawPath(using: .fillStroke)
} else {
context.fillPath()
}
} else {
context.fillEllipse(in: CGRect(origin: offset, size: badgeSize))
}
if let _ = solidColor {
} else {
blurredImage.draw(in: CGRect(origin: CGPoint(), size: badgeSize), blendMode: .sourceIn, alpha: 1.0)
context.setBlendMode(.normal)
}
var fontSize: CGFloat = floor(parameters.size.height * 0.48)
while true {
let string = NSAttributedString(string: parameters.text, font: Font.bold(fontSize), textColor: textColor)
let stringBounds = string.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil)
if stringBounds.width <= size.width - 5.0 * 2.0 || fontSize <= 2.0 {
string.draw(at: CGPoint(x: stringBounds.minX + floorToScreenPixels((size.width - stringBounds.width) / 2.0), y: stringBounds.minY + floorToScreenPixels((size.height - stringBounds.height) / 2.0)))
break
} else {
fontSize -= 1.0
}
}
if parameters.hasTimeoutIcon {
let lineWidth: CGFloat = 1.5
let lineInset: CGFloat = 2.0
let lineRadius: CGFloat = size.width * 0.5 - lineInset - lineWidth * 0.5
context.setLineWidth(lineWidth)
context.setStrokeColor(textColor.cgColor)
context.setLineCap(.round)
context.addArc(center: CGPoint(x: size.width * 0.5, y: size.height * 0.5), radius: lineRadius, startAngle: CGFloat.pi * 0.5, endAngle: -CGFloat.pi * 0.5, clockwise: false)
context.strokePath()
let sectionAngle: CGFloat = CGFloat.pi / 11.0
for i in 0 ..< 10 {
if i % 2 == 0 {
continue
}
let startAngle = CGFloat.pi * 0.5 - CGFloat(i) * sectionAngle - sectionAngle * 0.15
let endAngle = startAngle - sectionAngle * 0.75
context.addArc(center: CGPoint(x: size.width * 0.5, y: size.height * 0.5), radius: lineRadius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
context.strokePath()
}
}
/*if isLightImage {
context.setLineWidth(UIScreenPixel)
context.setStrokeColor(textColor.withMultipliedAlpha(1.0).cgColor)
context.strokeEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: UIScreenPixel * 0.5, dy: UIScreenPixel * 0.5))
}*/
UIGraphicsPopContext()
})
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,546 @@
import Foundation
import UIKit
import SwiftSignalKit
import Display
import ImageIO
import TelegramCore
import TinyThumbnail
import FastBlur
import Postbox
private let roundCorners = { () -> UIImage in
let diameter: CGFloat = 60.0
UIGraphicsBeginImageContextWithOptions(CGSize(width: diameter, height: diameter), false, 0.0)
let context = UIGraphicsGetCurrentContext()!
context.setBlendMode(.copy)
context.setFillColor(UIColor.black.cgColor)
context.fill(CGRect(origin: CGPoint(), size: CGSize(width: diameter, height: diameter)))
context.setFillColor(UIColor.clear.cgColor)
context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: diameter, height: diameter)))
let image = UIGraphicsGetImageFromCurrentImageContext()!.stretchableImage(withLeftCapWidth: Int(diameter / 2.0), topCapHeight: Int(diameter / 2.0))
UIGraphicsEndImageContext()
return image
}()
public enum PeerAvatarImageType {
case blurred
case complete
}
public func peerAvatarImageData(account: Account, peerReference: PeerReference?, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, synchronousLoad: Bool) -> Signal<(Data, PeerAvatarImageType)?, NoError>? {
return peerAvatarImageData(postbox: account.postbox, network: account.network, peerReference: peerReference, authorOfMessage: authorOfMessage, representation: representation, synchronousLoad: synchronousLoad)
}
public func peerAvatarImageData(postbox: Postbox, network: Network, peerReference: PeerReference?, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, synchronousLoad: Bool) -> Signal<(Data, PeerAvatarImageType)?, NoError>? {
if let smallProfileImage = representation {
let resourceData = postbox.mediaBox.resourceData(smallProfileImage.resource, attemptSynchronously: synchronousLoad)
let imageData = resourceData
|> take(1)
|> mapToSignal { maybeData -> Signal<(Data, PeerAvatarImageType)?, NoError> in
if maybeData.complete {
if let data = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path)) {
return .single((data, .complete))
} else {
return .single(nil)
}
} else {
return Signal { subscriber in
var emittedFirstData = false
if let miniData = representation?.immediateThumbnailData, let decodedData = decodeTinyThumbnail(data: miniData) {
emittedFirstData = true
subscriber.putNext((decodedData, .blurred))
}
let resourceDataDisposable = resourceData.start(next: { data in
if data.complete {
if let dataValue = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path)) {
subscriber.putNext((dataValue, .complete))
} else {
subscriber.putNext(nil)
}
subscriber.putCompletion()
} else {
if !emittedFirstData {
subscriber.putNext(nil)
}
}
}, error: { _ in
}, completed: {
subscriber.putCompletion()
})
var fetchedDataDisposable: Disposable?
if let peerReference = peerReference {
fetchedDataDisposable = fetchedMediaResource(mediaBox: postbox.mediaBox, userLocation: .other, userContentType: .avatar, reference: .avatar(peer: peerReference, resource: smallProfileImage.resource), statsCategory: .generic).start()
} else if let authorOfMessage = authorOfMessage {
fetchedDataDisposable = fetchedMediaResource(mediaBox: postbox.mediaBox, userLocation: .other, userContentType: .avatar, reference: .messageAuthorAvatar(message: authorOfMessage, resource: smallProfileImage.resource), statsCategory: .generic).start()
} else {
fetchedDataDisposable = fetchedMediaResource(mediaBox: postbox.mediaBox, userLocation: .other, userContentType: .avatar, reference: .standalone(resource: smallProfileImage.resource), statsCategory: .generic).start()
}
return ActionDisposable {
resourceDataDisposable.dispose()
fetchedDataDisposable?.dispose()
}
}
}
}
return imageData
} else {
return nil
}
}
public func peerAvatarCompleteImage(account: Account, peer: EnginePeer, forceProvidedRepresentation: Bool = false, representation: TelegramMediaImageRepresentation? = nil, size: CGSize, round: Bool = true, font: UIFont = avatarPlaceholderFont(size: 13.0), drawLetters: Bool = true, fullSize: Bool = false, blurred: Bool = false) -> Signal<UIImage?, NoError> {
return peerAvatarCompleteImage(
postbox: account.postbox,
network: account.network,
peer: peer,
forceProvidedRepresentation: forceProvidedRepresentation,
representation: representation,
size: size,
round: round,
font: font,
drawLetters: drawLetters,
fullSize: fullSize,
blurred: blurred
)
}
public func peerAvatarCompleteImage(postbox: Postbox, network: Network, peer: EnginePeer, forceProvidedRepresentation: Bool = false, representation: TelegramMediaImageRepresentation? = nil, size: CGSize, round: Bool = true, font: UIFont = avatarPlaceholderFont(size: 13.0), drawLetters: Bool = true, fullSize: Bool = false, blurred: Bool = false) -> Signal<UIImage?, NoError> {
let iconSignal: Signal<UIImage?, NoError>
let clipStyle: AvatarNodeClipStyle
if round {
if case let .channel(channel) = peer, channel.isForumOrMonoForum {
clipStyle = .roundedRect
} else {
clipStyle = .round
}
} else {
clipStyle = .none
}
let thumbnailRepresentation: TelegramMediaImageRepresentation?
if forceProvidedRepresentation {
thumbnailRepresentation = representation
} else {
thumbnailRepresentation = peer.profileImageRepresentations.first
}
if let signal = peerAvatarImage(postbox: postbox, network: network, peerReference: PeerReference(peer._asPeer()), authorOfMessage: nil, representation: thumbnailRepresentation, displayDimensions: size, clipStyle: clipStyle, blurred: blurred, inset: 0.0, emptyColor: nil, synchronousLoad: fullSize) {
if fullSize, let fullSizeSignal = peerAvatarImage(postbox: postbox, network: network, peerReference: PeerReference(peer._asPeer()), authorOfMessage: nil, representation: peer.profileImageRepresentations.last, displayDimensions: size, emptyColor: nil, synchronousLoad: true) {
iconSignal = combineLatest(.single(nil) |> then(signal), .single(nil) |> then(fullSizeSignal))
|> mapToSignal { thumbnailImage, fullSizeImage -> Signal<UIImage?, NoError> in
if let fullSizeImage = fullSizeImage {
return .single(fullSizeImage.0)
} else if let thumbnailImage = thumbnailImage {
return .single(thumbnailImage.0)
} else {
return .complete()
}
}
} else {
iconSignal = signal
|> map { imageVersions -> UIImage? in
return imageVersions?.0
}
}
} else {
let peerId = peer.id
var displayLetters = peer.displayLetters
if displayLetters.count == 2 && displayLetters[0].isSingleEmoji && displayLetters[1].isSingleEmoji {
displayLetters = [displayLetters[0]]
}
if !drawLetters {
displayLetters = []
}
iconSignal = Signal { subscriber in
let image = generateImage(size, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
drawPeerAvatarLetters(context: context, size: CGSize(width: size.width, height: size.height), round: round, font: font, letters: displayLetters, peerId: peerId, nameColor: peer.nameColor)
if blurred {
context.setFillColor(UIColor(rgb: 0x000000, alpha: 0.5).cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
}
})?.withRenderingMode(.alwaysOriginal)
subscriber.putNext(image)
subscriber.putCompletion()
return EmptyDisposable
}
}
return iconSignal
}
public func peerAvatarImage(account: Account, peerReference: PeerReference?, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0), clipStyle: AvatarNodeClipStyle = .round, blurred: Bool = false, inset: CGFloat = 0.0, emptyColor: UIColor? = nil, synchronousLoad: Bool = false, provideUnrounded: Bool = false, cutoutRect: CGRect? = nil) -> Signal<(UIImage, UIImage)?, NoError>? {
return peerAvatarImage(
postbox: account.postbox,
network: account.network,
peerReference: peerReference,
authorOfMessage: authorOfMessage,
representation: representation,
displayDimensions: displayDimensions,
clipStyle: clipStyle,
blurred: blurred,
inset: inset,
emptyColor: emptyColor,
synchronousLoad: synchronousLoad,
provideUnrounded: synchronousLoad,
cutoutRect: cutoutRect
)
}
public func peerAvatarImage(postbox: Postbox, network: Network, peerReference: PeerReference?, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0), clipStyle: AvatarNodeClipStyle = .round, blurred: Bool = false, inset: CGFloat = 0.0, emptyColor: UIColor? = nil, synchronousLoad: Bool = false, provideUnrounded: Bool = false, cutoutRect: CGRect? = nil) -> Signal<(UIImage, UIImage)?, NoError>? {
if let imageData = peerAvatarImageData(postbox: postbox, network: network, peerReference: peerReference, authorOfMessage: authorOfMessage, representation: representation, synchronousLoad: synchronousLoad) {
return imageData
|> mapToSignal { data -> Signal<(UIImage, UIImage)?, NoError> in
let generate = deferred { () -> Signal<(UIImage, UIImage)?, NoError> in
if emptyColor == nil && data == nil {
return .single(nil)
}
let roundedImage = generateImage(displayDimensions, contextGenerator: { size, context -> Void in
if let (data, dataType) = data {
if let imageSource = CGImageSourceCreateWithData(data as CFData, nil), var dataImage = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) {
context.clear(CGRect(origin: CGPoint(), size: displayDimensions))
context.setBlendMode(.copy)
switch clipStyle {
case .none:
break
case .round:
if displayDimensions.width != 60.0 {
context.addEllipse(in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
context.clip()
}
case .roundedRect:
context.addPath(UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: displayDimensions.width, height: displayDimensions.height).insetBy(dx: inset, dy: inset), cornerRadius: floor(displayDimensions.width * 0.25)).cgPath)
context.clip()
case .bubble:
let rect = CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset)
context.translateBy(x: rect.midX, y: rect.midY)
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: -rect.midX, y: -rect.midY)
AvatarNode.addAvatarBubblePath(context: context, rect: rect)
context.translateBy(x: rect.midX, y: rect.midY)
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: -rect.midX, y: -rect.midY)
context.clip()
}
var shouldBlur = false
if case .blurred = dataType {
shouldBlur = true
} else if blurred {
shouldBlur = true
}
if shouldBlur {
let imageContextSize = size.width > 200.0 ? CGSize(width: 192.0, height: 192.0) : CGSize(width: 64.0, height: 64.0)
if let imageContext = DrawingContext(size: imageContextSize, scale: 1.0, clear: true) {
imageContext.withFlippedContext { c in
c.draw(dataImage, in: CGRect(origin: CGPoint(), size: imageContextSize))
context.setBlendMode(.saturation)
context.setFillColor(UIColor(rgb: 0xffffff, alpha: 1.0).cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
context.setBlendMode(.copy)
}
telegramFastBlurMore(Int32(imageContext.size.width * imageContext.scale), Int32(imageContext.size.height * imageContext.scale), Int32(imageContext.bytesPerRow), imageContext.bytes)
if size.width > 200.0 {
telegramFastBlurMore(Int32(imageContext.size.width * imageContext.scale), Int32(imageContext.size.height * imageContext.scale), Int32(imageContext.bytesPerRow), imageContext.bytes)
telegramFastBlurMore(Int32(imageContext.size.width * imageContext.scale), Int32(imageContext.size.height * imageContext.scale), Int32(imageContext.bytesPerRow), imageContext.bytes)
telegramFastBlurMore(Int32(imageContext.size.width * imageContext.scale), Int32(imageContext.size.height * imageContext.scale), Int32(imageContext.bytesPerRow), imageContext.bytes)
telegramFastBlurMore(Int32(imageContext.size.width * imageContext.scale), Int32(imageContext.size.height * imageContext.scale), Int32(imageContext.bytesPerRow), imageContext.bytes)
}
dataImage = imageContext.generateImage()!.cgImage!
}
}
let filledSize = CGSize(width: dataImage.width, height: dataImage.height).aspectFilled(displayDimensions)
context.draw(dataImage, in: CGRect(origin: CGPoint(x: floor((displayDimensions.width - filledSize.width) / 2.0), y: floor((displayDimensions.height - filledSize.height) / 2.0)), size: filledSize).insetBy(dx: inset, dy: inset))
if blurred {
context.setBlendMode(.normal)
context.setFillColor(UIColor(rgb: 0x000000, alpha: 0.45).cgColor)
context.fill(CGRect(origin: CGPoint(), size: size))
context.setBlendMode(.copy)
}
switch clipStyle {
case .none:
break
case .round:
if displayDimensions.width == 60.0 {
context.setBlendMode(.destinationOut)
context.draw(roundCorners.cgImage!, in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
}
case .roundedRect:
break
case .bubble:
break
}
} else {
if let emptyColor = emptyColor {
context.clear(CGRect(origin: CGPoint(), size: displayDimensions))
context.setFillColor(emptyColor.cgColor)
switch clipStyle {
case .none:
context.fill(CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
case .round:
context.fillEllipse(in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
case .roundedRect:
context.beginPath()
context.addPath(UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: displayDimensions.width, height: displayDimensions.height).insetBy(dx: inset, dy: inset), cornerRadius: floor(displayDimensions.width * 0.25)).cgPath)
context.fillPath()
case .bubble:
let rect = CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset)
context.translateBy(x: rect.midX, y: rect.midY)
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: -rect.midX, y: -rect.midY)
AvatarNode.addAvatarBubblePath(context: context, rect: rect)
context.translateBy(x: rect.midX, y: rect.midY)
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: -rect.midX, y: -rect.midY)
context.clip()
}
}
}
} else if let emptyColor = emptyColor {
context.clear(CGRect(origin: CGPoint(), size: displayDimensions))
context.setFillColor(emptyColor.cgColor)
switch clipStyle {
case .none:
context.fill(CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
case .round:
context.fillEllipse(in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
case .roundedRect:
context.beginPath()
context.addPath(UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: displayDimensions.width, height: displayDimensions.height).insetBy(dx: inset, dy: inset), cornerRadius: floor(displayDimensions.width * 0.25)).cgPath)
context.fillPath()
case .bubble:
let rect = CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset)
context.translateBy(x: rect.midX, y: rect.midY)
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: -rect.midX, y: -rect.midY)
AvatarNode.addAvatarBubblePath(context: context, rect: rect)
context.translateBy(x: rect.midX, y: rect.midY)
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: -rect.midX, y: -rect.midY)
context.clip()
}
}
if let cutoutRect {
context.setBlendMode(.copy)
context.setFillColor(UIColor.clear.cgColor)
//TODO:fix
context.fillEllipse(in: cutoutRect)
}
})
let unroundedImage: UIImage?
if provideUnrounded {
unroundedImage = generateImage(displayDimensions, contextGenerator: { size, context -> Void in
if let (data, _) = data {
if let imageSource = CGImageSourceCreateWithData(data as CFData, nil), let dataImage = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) {
context.clear(CGRect(origin: CGPoint(), size: displayDimensions))
context.setBlendMode(.copy)
context.draw(dataImage, in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
} else {
if let emptyColor = emptyColor {
context.clear(CGRect(origin: CGPoint(), size: displayDimensions))
context.setFillColor(emptyColor.cgColor)
context.fill(CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
}
}
} else if let emptyColor = emptyColor {
context.clear(CGRect(origin: CGPoint(), size: displayDimensions))
context.setFillColor(emptyColor.cgColor)
switch clipStyle {
case .none:
context.fill(CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
case .round:
context.fillEllipse(in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
case .roundedRect:
context.beginPath()
context.addPath(UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: displayDimensions.width, height: displayDimensions.height).insetBy(dx: inset, dy: inset), cornerRadius: floor(displayDimensions.width * 0.25)).cgPath)
context.fillPath()
case .bubble:
let rect = CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset)
context.translateBy(x: rect.midX, y: rect.midY)
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: -rect.midX, y: -rect.midY)
AvatarNode.addAvatarBubblePath(context: context, rect: rect)
context.translateBy(x: rect.midX, y: rect.midY)
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: -rect.midX, y: -rect.midY)
context.clip()
}
}
})
} else {
unroundedImage = roundedImage
}
if let roundedImage = roundedImage, let unroundedImage = unroundedImage {
return .single((roundedImage, unroundedImage))
} else {
return .single(nil)
}
}
if synchronousLoad {
return generate
} else {
return generate |> runOn(Queue.concurrentDefaultQueue())
}
}
} else {
return nil
}
}
public func drawPeerAvatarLetters(context: CGContext, size: CGSize, round: Bool = true, font: UIFont, letters: [String], peerId: EnginePeer.Id, nameColor: PeerColor?) {
if round {
context.beginPath()
context.addEllipse(in: CGRect(x: 0.0, y: 0.0, width: size.width, height:
size.height))
context.clip()
}
let colorIndex: Int
if peerId.namespace == .max {
colorIndex = -1
} else {
colorIndex = Int(clamping: abs(peerId.id._internalGetInt64Value()))
}
let colorsArray: NSArray
if colorIndex == -1 {
colorsArray = AvatarNode.grayscaleColors.map(\.cgColor) as NSArray
} else {
var index = colorIndex % AvatarNode.gradientColors.count
if let nameColor {
switch nameColor {
case let .preset(peerNameColor):
index = Int(peerNameColor.rawValue) % AvatarNode.gradientColors.count
case let .collectible(peerCollectibleColor):
let _ = peerCollectibleColor
break
}
}
colorsArray = AvatarNode.gradientColors[index].map(\.cgColor) as NSArray
}
var locations: [CGFloat] = [1.0, 0.0]
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: colorsArray, locations: &locations)!
context.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
context.resetClip()
context.setBlendMode(.normal)
let string = letters.count == 0 ? "" : (letters[0] + (letters.count == 1 ? "" : letters[1]))
let attributedString = NSAttributedString(string: string, attributes: [NSAttributedString.Key.font: font, NSAttributedString.Key.foregroundColor: UIColor.white])
let line = CTLineCreateWithAttributedString(attributedString)
let lineBounds = CTLineGetBoundsWithOptions(line, .useGlyphPathBounds)
let lineOffset = CGPoint(x: string == "B" ? 1.0 : 0.0, y: 0.0)
let lineOrigin = CGPoint(x: floorToScreenPixels(-lineBounds.origin.x + (size.width - lineBounds.size.width) / 2.0) + lineOffset.x, y: floorToScreenPixels(-lineBounds.origin.y + (size.height - lineBounds.size.height) / 2.0))
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
let textPosition = context.textPosition
context.translateBy(x: lineOrigin.x, y: lineOrigin.y)
CTLineDraw(line, context)
context.translateBy(x: -lineOrigin.x, y: -lineOrigin.y)
context.textPosition = textPosition
}
public enum AvatarBackgroundColor {
case blue
case yellow
case green
case purple
case red
case violet
}
public func generateAvatarImage(size: CGSize, icon: UIImage?, iconScale: CGFloat = 1.0, cornerRadius: CGFloat? = nil, circleCorners: Bool = false, color: AvatarBackgroundColor, customColors: [UIColor]? = nil, diagonal: Bool = false) -> UIImage? {
return generateImage(size, rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.beginPath()
if let cornerRadius {
if circleCorners {
let roundedRect = CGPath(roundedRect: CGRect(origin: .zero, size: size), cornerWidth: cornerRadius, cornerHeight: cornerRadius, transform: nil)
context.addPath(roundedRect)
} else {
let roundedRect = UIBezierPath(roundedRect: CGRect(origin: .zero, size: size), cornerRadius: cornerRadius)
context.addPath(roundedRect.cgPath)
}
} else {
context.addEllipse(in: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
}
context.clip()
let colorIndex: Int
switch color {
case .blue:
colorIndex = 5
case .yellow:
colorIndex = 1
case .green:
colorIndex = 3
case .purple:
colorIndex = 2
case .red:
colorIndex = 0
case .violet:
colorIndex = 6
}
let colorsArray: NSArray
if let customColors {
colorsArray = customColors.map(\.cgColor) as NSArray
} else {
if colorIndex == -1 {
colorsArray = AvatarNode.grayscaleColors.map(\.cgColor) as NSArray
} else {
colorsArray = AvatarNode.gradientColors[colorIndex % AvatarNode.gradientColors.count].map(\.cgColor) as NSArray
}
}
var locations: [CGFloat] = []
let delta = 1.0 / CGFloat(colorsArray.count - 1)
for i in 0 ..< colorsArray.count {
locations.append(1.0 - delta * CGFloat(i))
}
let colorSpace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradient(colorsSpace: colorSpace, colors: colorsArray, locations: &locations)!
if diagonal {
context.drawLinearGradient(gradient, start: CGPoint(x: size.width, y: 0.0), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
} else {
context.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
}
context.resetClip()
context.setBlendMode(.normal)
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
context.scaleBy(x: 1.0, y: -1.0)
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
if let icon = icon {
let iconSize = CGSize(width: icon.size.width * iconScale, height: icon.size.height * iconScale)
let iconFrame = CGRect(origin: CGPoint(x: floor((size.width - iconSize.width) / 2.0), y: floor((size.height - iconSize.height) / 2.0)), size: iconSize)
context.draw(icon.cgImage!, in: iconFrame)
}
})
}