mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
532 lines
33 KiB
Swift
532 lines
33 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import Display
|
|
import TelegramCore
|
|
|
|
public enum MessageBubbleImageNeighbors {
|
|
case none
|
|
case top(side: Bool)
|
|
case bottom
|
|
case both
|
|
case side
|
|
case extracted
|
|
}
|
|
|
|
public func messageSingleBubbleLikeImage(fillColor: UIColor, strokeColor: UIColor) -> UIImage {
|
|
let diameter: CGFloat = 36.0
|
|
return generateImage(CGSize(width: 36.0, height: diameter), contextGenerator: { size, context in
|
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
|
|
|
let lineWidth: CGFloat = 0.5
|
|
|
|
context.setFillColor(strokeColor.cgColor)
|
|
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
|
context.setFillColor(fillColor.cgColor)
|
|
context.fillEllipse(in: CGRect(origin: CGPoint(x: lineWidth, y: lineWidth), size: CGSize(width: size.width - lineWidth * 2.0, height: size.height - lineWidth * 2.0)))
|
|
})!.stretchableImage(withLeftCapWidth: Int(diameter / 2.0), topCapHeight: Int(diameter / 2.0))
|
|
}
|
|
|
|
private let minRadiusForFullTailCorner: CGFloat = 14.0
|
|
|
|
func mediaBubbleCornerImage(incoming: Bool, radius: CGFloat, inset: CGFloat) -> UIImage {
|
|
let imageSize = CGSize(width: radius + 7.0, height: 8.0)
|
|
let fixedMainDiameter: CGFloat = 33.0
|
|
|
|
let formContext = DrawingContext(size: imageSize)!
|
|
formContext.withFlippedContext { context in
|
|
context.clear(CGRect(origin: CGPoint(), size: imageSize))
|
|
context.translateBy(x: imageSize.width / 2.0, y: imageSize.height / 2.0)
|
|
context.scaleBy(x: incoming ? -1.0 : 1.0, y: -1.0)
|
|
context.translateBy(x: -imageSize.width / 2.0, y: -imageSize.height / 2.0)
|
|
|
|
context.setFillColor(UIColor.black.cgColor)
|
|
|
|
let bottomEllipse = CGRect(origin: CGPoint(x: 24.0, y: 16.0), size: CGSize(width: 27.0, height: 17.0)).insetBy(dx: inset, dy: inset).offsetBy(dx: inset, dy: inset)
|
|
let topEllipse = CGRect(origin: CGPoint(x: 33.0, y: 14.0), size: CGSize(width: 23.0, height: 21.0)).insetBy(dx: -inset, dy: -inset).offsetBy(dx: inset, dy: inset)
|
|
|
|
context.translateBy(x: -fixedMainDiameter + imageSize.width - 6.0, y: -fixedMainDiameter + imageSize.height)
|
|
|
|
let topLeftRadius: CGFloat = 2.0
|
|
let topRightRadius: CGFloat = 2.0
|
|
let bottomLeftRadius: CGFloat = 2.0
|
|
let bottomRightRadius: CGFloat = radius
|
|
|
|
context.move(to: CGPoint(x: 0.0, y: topLeftRadius))
|
|
context.addArc(tangent1End: CGPoint(x: 0.0, y: 0.0), tangent2End: CGPoint(x: topLeftRadius, y: 0.0), radius: topLeftRadius)
|
|
context.addLine(to: CGPoint(x: fixedMainDiameter - topRightRadius, y: 0.0))
|
|
context.addArc(tangent1End: CGPoint(x: fixedMainDiameter, y: 0.0), tangent2End: CGPoint(x: fixedMainDiameter, y: topRightRadius), radius: topRightRadius)
|
|
context.addLine(to: CGPoint(x: fixedMainDiameter, y: fixedMainDiameter - bottomRightRadius))
|
|
context.addArc(tangent1End: CGPoint(x: fixedMainDiameter, y: fixedMainDiameter), tangent2End: CGPoint(x: fixedMainDiameter - bottomRightRadius, y: fixedMainDiameter), radius: bottomRightRadius)
|
|
context.addLine(to: CGPoint(x: bottomLeftRadius, y: fixedMainDiameter))
|
|
context.addArc(tangent1End: CGPoint(x: 0.0, y: fixedMainDiameter), tangent2End: CGPoint(x: 0.0, y: fixedMainDiameter - bottomLeftRadius), radius: bottomLeftRadius)
|
|
context.addLine(to: CGPoint(x: 0.0, y: topLeftRadius))
|
|
context.fillPath()
|
|
|
|
if radius >= minRadiusForFullTailCorner {
|
|
context.move(to: CGPoint(x: bottomEllipse.minX, y: bottomEllipse.midY))
|
|
context.addQuadCurve(to: CGPoint(x: bottomEllipse.midX, y: bottomEllipse.maxY), control: CGPoint(x: bottomEllipse.minX, y: bottomEllipse.maxY))
|
|
context.addQuadCurve(to: CGPoint(x: bottomEllipse.maxX, y: bottomEllipse.midY), control: CGPoint(x: bottomEllipse.maxX, y: bottomEllipse.maxY))
|
|
context.fillPath()
|
|
} else {
|
|
context.fill(CGRect(origin: CGPoint(x: bottomEllipse.minX - 5.0, y: bottomEllipse.midY), size: CGSize(width: bottomEllipse.width + 5.0, height: bottomEllipse.height / 2.0)))
|
|
}
|
|
context.fill(CGRect(origin: CGPoint(x: fixedMainDiameter / 2.0, y: floor(fixedMainDiameter / 2.0)), size: CGSize(width: fixedMainDiameter / 2.0, height: ceil(bottomEllipse.midY) - floor(fixedMainDiameter / 2.0))))
|
|
context.setFillColor(UIColor.clear.cgColor)
|
|
context.setBlendMode(.copy)
|
|
context.fillEllipse(in: topEllipse)
|
|
}
|
|
|
|
return formContext.generateImage()!
|
|
}
|
|
|
|
public func messageBubbleImage(maxCornerRadius: CGFloat, minCornerRadius: CGFloat, incoming: Bool, fillColor: UIColor, strokeColor: UIColor, neighbors: MessageBubbleImageNeighbors, theme: PresentationThemeChat, wallpaper: TelegramWallpaper, knockout knockoutValue: Bool, mask: Bool = false, extendedEdges: Bool = false, onlyOutline: Bool = false, onlyShadow: Bool = false, alwaysFillColor: Bool = false) -> UIImage {
|
|
let bubbleColors = incoming ? theme.message.incoming : theme.message.outgoing
|
|
return messageBubbleImage(maxCornerRadius: maxCornerRadius, minCornerRadius: minCornerRadius, incoming: incoming, fillColor: fillColor, strokeColor: strokeColor, neighbors: neighbors, shadow: bubbleColors.bubble.withWallpaper.shadow, wallpaper: wallpaper, knockout: knockoutValue, mask: mask, extendedEdges: extendedEdges, onlyOutline: onlyOutline, onlyShadow: onlyShadow, alwaysFillColor: alwaysFillColor)
|
|
}
|
|
|
|
public func messageBubbleArguments(maxCornerRadius: CGFloat, minCornerRadius: CGFloat, incoming: Bool, neighbors: MessageBubbleImageNeighbors) -> (topLeftRadius: CGFloat, topRightRadius: CGFloat, bottomLeftRadius: CGFloat, bottomRightRadius: CGFloat, drawTail: Bool) {
|
|
var topLeftRadius: CGFloat
|
|
var topRightRadius: CGFloat
|
|
var bottomLeftRadius: CGFloat
|
|
var bottomRightRadius: CGFloat
|
|
var drawTail: Bool
|
|
|
|
switch neighbors {
|
|
case .none:
|
|
topLeftRadius = maxCornerRadius
|
|
topRightRadius = maxCornerRadius
|
|
bottomLeftRadius = maxCornerRadius
|
|
bottomRightRadius = maxCornerRadius
|
|
drawTail = true
|
|
case .both:
|
|
topLeftRadius = maxCornerRadius
|
|
topRightRadius = minCornerRadius
|
|
bottomLeftRadius = maxCornerRadius
|
|
bottomRightRadius = minCornerRadius
|
|
drawTail = false
|
|
case .bottom:
|
|
topLeftRadius = maxCornerRadius
|
|
topRightRadius = minCornerRadius
|
|
bottomLeftRadius = maxCornerRadius
|
|
bottomRightRadius = maxCornerRadius
|
|
drawTail = true
|
|
case .side:
|
|
topLeftRadius = maxCornerRadius
|
|
topRightRadius = maxCornerRadius
|
|
bottomLeftRadius = minCornerRadius
|
|
bottomRightRadius = minCornerRadius
|
|
drawTail = false
|
|
case let .top(side):
|
|
topLeftRadius = maxCornerRadius
|
|
topRightRadius = maxCornerRadius
|
|
bottomLeftRadius = side ? minCornerRadius : maxCornerRadius
|
|
bottomRightRadius = minCornerRadius
|
|
drawTail = false
|
|
case .extracted:
|
|
topLeftRadius = maxCornerRadius
|
|
topRightRadius = maxCornerRadius
|
|
bottomLeftRadius = maxCornerRadius
|
|
bottomRightRadius = maxCornerRadius
|
|
drawTail = false
|
|
}
|
|
|
|
if incoming {
|
|
var tmp = topRightRadius
|
|
topRightRadius = topLeftRadius
|
|
topLeftRadius = tmp
|
|
|
|
tmp = bottomRightRadius
|
|
bottomRightRadius = bottomLeftRadius
|
|
bottomLeftRadius = tmp
|
|
}
|
|
|
|
return (topLeftRadius, topRightRadius, bottomLeftRadius, bottomRightRadius, drawTail)
|
|
}
|
|
|
|
public func messageBubbleImage(maxCornerRadius: CGFloat, minCornerRadius: CGFloat, incoming: Bool, fillColor: UIColor, strokeColor: UIColor, neighbors: MessageBubbleImageNeighbors, shadow: PresentationThemeBubbleShadow?, wallpaper: TelegramWallpaper, knockout knockoutValue: Bool, mask: Bool = false, extendedEdges: Bool = false, onlyOutline: Bool = false, onlyShadow: Bool = false, alwaysFillColor: Bool = false) -> UIImage {
|
|
let topLeftRadius: CGFloat
|
|
let topRightRadius: CGFloat
|
|
let bottomLeftRadius: CGFloat
|
|
let bottomRightRadius: CGFloat
|
|
let drawTail: Bool
|
|
|
|
switch neighbors {
|
|
case .none:
|
|
topLeftRadius = maxCornerRadius
|
|
topRightRadius = maxCornerRadius
|
|
bottomLeftRadius = maxCornerRadius
|
|
bottomRightRadius = maxCornerRadius
|
|
drawTail = true
|
|
case .both:
|
|
topLeftRadius = maxCornerRadius
|
|
topRightRadius = minCornerRadius
|
|
bottomLeftRadius = maxCornerRadius
|
|
bottomRightRadius = minCornerRadius
|
|
drawTail = false
|
|
case .bottom:
|
|
topLeftRadius = maxCornerRadius
|
|
topRightRadius = minCornerRadius
|
|
bottomLeftRadius = maxCornerRadius
|
|
bottomRightRadius = maxCornerRadius
|
|
drawTail = true
|
|
case .side:
|
|
topLeftRadius = maxCornerRadius
|
|
topRightRadius = maxCornerRadius
|
|
bottomLeftRadius = minCornerRadius
|
|
bottomRightRadius = minCornerRadius
|
|
drawTail = false
|
|
case let .top(side):
|
|
topLeftRadius = maxCornerRadius
|
|
topRightRadius = maxCornerRadius
|
|
bottomLeftRadius = side ? minCornerRadius : maxCornerRadius
|
|
bottomRightRadius = minCornerRadius
|
|
drawTail = false
|
|
case .extracted:
|
|
topLeftRadius = maxCornerRadius
|
|
topRightRadius = maxCornerRadius
|
|
bottomLeftRadius = maxCornerRadius
|
|
bottomRightRadius = maxCornerRadius
|
|
drawTail = false
|
|
}
|
|
|
|
let fixedMainDiameter: CGFloat = 33.0
|
|
let innerSize = CGSize(width: fixedMainDiameter + 6.0, height: fixedMainDiameter)
|
|
let strokeInset: CGFloat = 1.0
|
|
let sourceRawSize = CGSize(width: innerSize.width + strokeInset * 2.0, height: innerSize.height + strokeInset * 2.0)
|
|
let additionalInset: CGFloat = onlyShadow ? 10.0 : 1.0
|
|
let imageSize = CGSize(width: sourceRawSize.width + additionalInset * 2.0, height: sourceRawSize.height + additionalInset * 2.0)
|
|
let outgoingStretchPoint: (x: Int, y: Int) = (Int(additionalInset + strokeInset + round(fixedMainDiameter / 2.0)) - 1, Int(additionalInset + strokeInset + round(fixedMainDiameter / 2.0)))
|
|
let incomingStretchPoint: (x: Int, y: Int) = (Int(sourceRawSize.width) - outgoingStretchPoint.x + Int(additionalInset), outgoingStretchPoint.y)
|
|
|
|
let knockout = knockoutValue && !mask
|
|
|
|
let rawSize = imageSize
|
|
|
|
let bottomEllipse = CGRect(origin: CGPoint(x: 24.0, y: 16.0), size: CGSize(width: 27.0, height: 17.0))
|
|
let topEllipse = CGRect(origin: CGPoint(x: 33.0, y: 14.0), size: CGSize(width: 23.0, height: 21.0))
|
|
|
|
let formContext = DrawingContext(size: imageSize)!
|
|
formContext.withFlippedContext { context in
|
|
context.clear(CGRect(origin: CGPoint(), size: rawSize))
|
|
context.translateBy(x: additionalInset + strokeInset, y: additionalInset + strokeInset)
|
|
|
|
context.setFillColor(UIColor.black.cgColor)
|
|
|
|
context.move(to: CGPoint(x: 0.0, y: topLeftRadius))
|
|
context.addArc(tangent1End: CGPoint(x: 0.0, y: 0.0), tangent2End: CGPoint(x: topLeftRadius, y: 0.0), radius: topLeftRadius)
|
|
context.addLine(to: CGPoint(x: fixedMainDiameter - topRightRadius, y: 0.0))
|
|
context.addArc(tangent1End: CGPoint(x: fixedMainDiameter, y: 0.0), tangent2End: CGPoint(x: fixedMainDiameter, y: topRightRadius), radius: topRightRadius)
|
|
context.addLine(to: CGPoint(x: fixedMainDiameter, y: fixedMainDiameter - bottomRightRadius))
|
|
context.addArc(tangent1End: CGPoint(x: fixedMainDiameter, y: fixedMainDiameter), tangent2End: CGPoint(x: fixedMainDiameter - bottomRightRadius, y: fixedMainDiameter), radius: bottomRightRadius)
|
|
context.addLine(to: CGPoint(x: bottomLeftRadius, y: fixedMainDiameter))
|
|
context.addArc(tangent1End: CGPoint(x: 0.0, y: fixedMainDiameter), tangent2End: CGPoint(x: 0.0, y: fixedMainDiameter - bottomLeftRadius), radius: bottomLeftRadius)
|
|
context.addLine(to: CGPoint(x: 0.0, y: topLeftRadius))
|
|
context.fillPath()
|
|
|
|
if drawTail {
|
|
if maxCornerRadius >= minRadiusForFullTailCorner {
|
|
context.move(to: CGPoint(x: bottomEllipse.minX, y: bottomEllipse.midY))
|
|
context.addQuadCurve(to: CGPoint(x: bottomEllipse.midX, y: bottomEllipse.maxY), control: CGPoint(x: bottomEllipse.minX, y: bottomEllipse.maxY))
|
|
context.addQuadCurve(to: CGPoint(x: bottomEllipse.maxX, y: bottomEllipse.midY), control: CGPoint(x: bottomEllipse.maxX, y: bottomEllipse.maxY))
|
|
context.fillPath()
|
|
} else {
|
|
context.fill(CGRect(origin: CGPoint(x: bottomEllipse.minX - 2.0, y: bottomEllipse.midY), size: CGSize(width: bottomEllipse.width + 2.0, height: bottomEllipse.height / 2.0)))
|
|
}
|
|
context.fill(CGRect(origin: CGPoint(x: fixedMainDiameter / 2.0, y: floor(fixedMainDiameter / 2.0)), size: CGSize(width: fixedMainDiameter / 2.0, height: ceil(bottomEllipse.midY) - floor(fixedMainDiameter / 2.0))))
|
|
context.setFillColor(UIColor.clear.cgColor)
|
|
context.setBlendMode(.copy)
|
|
context.fillEllipse(in: topEllipse)
|
|
}
|
|
}
|
|
let formImage = formContext.generateImage()!
|
|
|
|
let outlineContext = DrawingContext(size: imageSize)!
|
|
outlineContext.withFlippedContext { context in
|
|
context.clear(CGRect(origin: CGPoint(), size: rawSize))
|
|
context.translateBy(x: additionalInset + strokeInset, y: additionalInset + strokeInset)
|
|
|
|
context.setStrokeColor(UIColor.black.cgColor)
|
|
let borderWidth: CGFloat
|
|
let borderOffset: CGFloat
|
|
|
|
let innerExtension: CGFloat
|
|
if knockout && !mask {
|
|
innerExtension = 0.25
|
|
} else {
|
|
innerExtension = 0.25
|
|
}
|
|
|
|
if abs(UIScreenPixel - 0.5) < CGFloat.ulpOfOne {
|
|
borderWidth = UIScreenPixel + innerExtension
|
|
borderOffset = -innerExtension / 2.0 + UIScreenPixel / 2.0
|
|
} else {
|
|
borderWidth = UIScreenPixel + innerExtension
|
|
borderOffset = -innerExtension / 2.0// + UIScreenPixel * 2.0 / 2.0
|
|
}
|
|
context.setLineWidth(borderWidth)
|
|
|
|
context.move(to: CGPoint(x: -borderOffset, y: topLeftRadius + borderOffset))
|
|
context.addArc(tangent1End: CGPoint(x: -borderOffset, y: -borderOffset), tangent2End: CGPoint(x: topLeftRadius + borderOffset, y: -borderOffset), radius: topLeftRadius + borderOffset * 2.0)
|
|
context.addLine(to: CGPoint(x: fixedMainDiameter - topRightRadius - borderOffset, y: -borderOffset))
|
|
context.addArc(tangent1End: CGPoint(x: fixedMainDiameter + borderOffset, y: -borderOffset), tangent2End: CGPoint(x: fixedMainDiameter + borderOffset, y: topRightRadius + borderOffset), radius: topRightRadius + borderOffset * 2.0)
|
|
context.addLine(to: CGPoint(x: fixedMainDiameter + borderOffset, y: fixedMainDiameter - bottomRightRadius - borderOffset))
|
|
context.addArc(tangent1End: CGPoint(x: fixedMainDiameter + borderOffset, y: fixedMainDiameter + borderOffset), tangent2End: CGPoint(x: fixedMainDiameter - bottomRightRadius - borderOffset, y: fixedMainDiameter + borderOffset), radius: bottomRightRadius + borderOffset * 2.0)
|
|
context.addLine(to: CGPoint(x: bottomLeftRadius + borderOffset, y: fixedMainDiameter + borderOffset))
|
|
context.addArc(tangent1End: CGPoint(x: -borderOffset, y: fixedMainDiameter + borderOffset), tangent2End: CGPoint(x: -borderOffset, y: fixedMainDiameter - bottomLeftRadius - borderOffset), radius: bottomLeftRadius + borderOffset * 2.0)
|
|
context.closePath()
|
|
context.strokePath()
|
|
|
|
if drawTail {
|
|
let outlineBottomEllipse = bottomEllipse.insetBy(dx: -borderOffset, dy: -borderOffset)
|
|
let outlineInnerTopEllipse = topEllipse.insetBy(dx: borderOffset, dy: borderOffset)
|
|
|
|
context.setBlendMode(.copy)
|
|
context.setFillColor(UIColor.clear.cgColor)
|
|
|
|
if maxCornerRadius >= minRadiusForFullTailCorner {
|
|
context.move(to: CGPoint(x: bottomEllipse.minX, y: bottomEllipse.midY))
|
|
context.addQuadCurve(to: CGPoint(x: bottomEllipse.midX, y: bottomEllipse.maxY), control: CGPoint(x: bottomEllipse.minX, y: bottomEllipse.maxY))
|
|
context.addQuadCurve(to: CGPoint(x: bottomEllipse.maxX, y: bottomEllipse.midY), control: CGPoint(x: bottomEllipse.maxX, y: bottomEllipse.maxY))
|
|
context.fillPath()
|
|
} else {
|
|
context.fill(CGRect(origin: CGPoint(x: bottomEllipse.minX - 2.0, y: floor(bottomEllipse.midY)), size: CGSize(width: bottomEllipse.width + 2.0, height: ceil(bottomEllipse.height / 2.0))))
|
|
}
|
|
context.fill(CGRect(origin: CGPoint(x: floor(fixedMainDiameter / 2.0), y: fixedMainDiameter / 2.0), size: CGSize(width: fixedMainDiameter / 2.0 + borderWidth, height: ceil(bottomEllipse.midY) - floor(fixedMainDiameter / 2.0))))
|
|
|
|
context.setBlendMode(.normal)
|
|
context.move(to: CGPoint(x: fixedMainDiameter + borderOffset, y: fixedMainDiameter / 2.0))
|
|
context.addLine(to: CGPoint(x: fixedMainDiameter + borderOffset, y: outlineBottomEllipse.midY))
|
|
context.strokePath()
|
|
|
|
let bubbleTailContext = DrawingContext(size: imageSize)!
|
|
bubbleTailContext.withFlippedContext { context in
|
|
context.clear(CGRect(origin: CGPoint(), size: rawSize))
|
|
context.translateBy(x: additionalInset + strokeInset, y: additionalInset + strokeInset)
|
|
|
|
context.setStrokeColor(UIColor.black.cgColor)
|
|
context.setLineWidth(borderWidth)
|
|
|
|
if maxCornerRadius >= minRadiusForFullTailCorner {
|
|
context.move(to: CGPoint(x: outlineBottomEllipse.minX, y: outlineBottomEllipse.midY))
|
|
context.addQuadCurve(to: CGPoint(x: outlineBottomEllipse.midX, y: outlineBottomEllipse.maxY), control: CGPoint(x: outlineBottomEllipse.minX, y: outlineBottomEllipse.maxY))
|
|
context.addQuadCurve(to: CGPoint(x: outlineBottomEllipse.maxX, y: outlineBottomEllipse.midY), control: CGPoint(x: outlineBottomEllipse.maxX, y: outlineBottomEllipse.maxY))
|
|
} else {
|
|
context.move(to: CGPoint(x: outlineBottomEllipse.minX - 2.0, y: outlineBottomEllipse.maxY))
|
|
context.addLine(to: CGPoint(x: outlineBottomEllipse.minX, y: outlineBottomEllipse.maxY))
|
|
context.addLine(to: CGPoint(x: outlineBottomEllipse.maxX, y: outlineBottomEllipse.maxY))
|
|
}
|
|
context.strokePath()
|
|
context.setFillColor(UIColor.clear.cgColor)
|
|
context.setBlendMode(.copy)
|
|
context.fillEllipse(in: outlineInnerTopEllipse)
|
|
|
|
context.move(to: CGPoint(x: 0.0, y: topLeftRadius))
|
|
context.addArc(tangent1End: CGPoint(x: 0.0, y: 0.0), tangent2End: CGPoint(x: topLeftRadius, y: 0.0), radius: topLeftRadius)
|
|
context.addLine(to: CGPoint(x: fixedMainDiameter - topRightRadius, y: 0.0))
|
|
context.addArc(tangent1End: CGPoint(x: fixedMainDiameter, y: 0.0), tangent2End: CGPoint(x: fixedMainDiameter, y: topRightRadius), radius: topRightRadius)
|
|
context.addLine(to: CGPoint(x: fixedMainDiameter, y: fixedMainDiameter - bottomRightRadius))
|
|
context.addArc(tangent1End: CGPoint(x: fixedMainDiameter, y: fixedMainDiameter), tangent2End: CGPoint(x: fixedMainDiameter - bottomRightRadius, y: fixedMainDiameter), radius: bottomRightRadius)
|
|
context.addLine(to: CGPoint(x: bottomLeftRadius, y: fixedMainDiameter))
|
|
context.addArc(tangent1End: CGPoint(x: 0.0, y: fixedMainDiameter), tangent2End: CGPoint(x: 0.0, y: fixedMainDiameter - bottomLeftRadius), radius: bottomLeftRadius)
|
|
context.addLine(to: CGPoint(x: 0.0, y: topLeftRadius))
|
|
context.fillPath()
|
|
|
|
let bottomEllipseMask = generateImage(bottomEllipse.insetBy(dx: -1.0, dy: -1.0).size, contextGenerator: { size, context in
|
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
|
context.setFillColor(UIColor.black.cgColor)
|
|
if maxCornerRadius >= minRadiusForFullTailCorner {
|
|
context.fillEllipse(in: CGRect(origin: CGPoint(x: 1.0 - borderOffset, y: 1.0 - borderOffset), size: CGSize(width: outlineBottomEllipse.width, height: outlineBottomEllipse.height)))
|
|
} else {
|
|
context.fill(CGRect(origin: CGPoint(x: 1.0 - borderOffset, y: 1.0 - borderOffset), size: CGSize(width: outlineBottomEllipse.width, height: outlineBottomEllipse.height)))
|
|
}
|
|
context.clear(CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height / 2.0)))
|
|
})!
|
|
|
|
context.clip(to: bottomEllipse.insetBy(dx: -1.0, dy: -1.0), mask: bottomEllipseMask.cgImage!)
|
|
context.strokeEllipse(in: outlineInnerTopEllipse)
|
|
context.resetClip()
|
|
}
|
|
|
|
context.translateBy(x: -(additionalInset + strokeInset), y: -(additionalInset + strokeInset))
|
|
context.draw(bubbleTailContext.generateImage()!.cgImage!, in: CGRect(origin: CGPoint(), size: rawSize))
|
|
context.translateBy(x: additionalInset + strokeInset, y: additionalInset + strokeInset)
|
|
}
|
|
}
|
|
let outlineImage = generateImage(outlineContext.size, contextGenerator: { size, context in
|
|
context.setBlendMode(.copy)
|
|
let image = outlineContext.generateImage()!
|
|
context.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: size))
|
|
})!
|
|
|
|
let drawingContext = DrawingContext(size: imageSize)!
|
|
drawingContext.withFlippedContext { context in
|
|
if onlyShadow {
|
|
context.clear(CGRect(origin: CGPoint(), size: rawSize))
|
|
|
|
if let shadow = shadow {
|
|
context.translateBy(x: rawSize.width / 2.0, y: rawSize.height / 2.0)
|
|
context.scaleBy(x: incoming ? -1.0 : 1.0, y: -1.0)
|
|
context.translateBy(x: -rawSize.width / 2.0, y: -rawSize.height / 2.0)
|
|
|
|
context.setShadow(offset: CGSize(width: 0.0, height: -shadow.verticalOffset), blur: shadow.radius, color: shadow.color.cgColor)
|
|
context.draw(formImage.cgImage!, in: CGRect(origin: CGPoint(), size: rawSize))
|
|
|
|
context.setBlendMode(.copy)
|
|
context.setFillColor(UIColor.clear.cgColor)
|
|
context.clip(to: CGRect(origin: CGPoint(), size: rawSize), mask: formImage.cgImage!)
|
|
context.fill(CGRect(origin: CGPoint(), size: rawSize))
|
|
}
|
|
} else {
|
|
var drawWithClearColor = false
|
|
|
|
if knockout {
|
|
drawWithClearColor = !mask
|
|
if case let .color(color) = wallpaper {
|
|
context.setFillColor(UIColor(rgb: UInt32(color)).cgColor)
|
|
context.fill(CGRect(origin: CGPoint(), size: rawSize))
|
|
} else {
|
|
context.clear(CGRect(origin: CGPoint(), size: rawSize))
|
|
}
|
|
} else {
|
|
context.clear(CGRect(origin: CGPoint(), size: rawSize))
|
|
}
|
|
|
|
if drawWithClearColor {
|
|
context.setBlendMode(.copy)
|
|
context.setFillColor(UIColor.clear.cgColor)
|
|
} else {
|
|
context.setBlendMode(.normal)
|
|
context.setFillColor(fillColor.cgColor)
|
|
}
|
|
|
|
context.saveGState()
|
|
|
|
context.translateBy(x: rawSize.width / 2.0, y: rawSize.height / 2.0)
|
|
context.scaleBy(x: incoming ? -1.0 : 1.0, y: -1.0)
|
|
context.translateBy(x: -rawSize.width / 2.0, y: -rawSize.height / 2.0)
|
|
|
|
if !onlyOutline {
|
|
context.clip(to: CGRect(origin: CGPoint(), size: rawSize), mask: formImage.cgImage!)
|
|
context.fill(CGRect(origin: CGPoint(), size: rawSize))
|
|
|
|
if alwaysFillColor && drawWithClearColor {
|
|
context.setBlendMode(.normal)
|
|
context.setFillColor(fillColor.cgColor)
|
|
context.fill(CGRect(origin: CGPoint(), size: rawSize))
|
|
}
|
|
} else {
|
|
context.setFillColor(strokeColor.cgColor)
|
|
context.clip(to: CGRect(origin: CGPoint(), size: rawSize), mask: outlineImage.cgImage!)
|
|
context.fill(CGRect(origin: CGPoint(), size: rawSize))
|
|
}
|
|
|
|
context.restoreGState()
|
|
}
|
|
}
|
|
|
|
return drawingContext.generateImage()!.stretchableImage(withLeftCapWidth: incoming ? incomingStretchPoint.x : outgoingStretchPoint.x, topCapHeight: incoming ? incomingStretchPoint.y : outgoingStretchPoint.y)
|
|
}
|
|
|
|
public enum MessageBubbleActionButtonPosition {
|
|
case middle
|
|
case bottomLeft
|
|
case bottomRight
|
|
case bottomSingle
|
|
}
|
|
|
|
public func messageBubbleActionButtonImage(color: UIColor, strokeColor: UIColor, position: MessageBubbleActionButtonPosition, bubbleCorners: PresentationChatBubbleCorners) -> UIImage {
|
|
let largeRadius: CGFloat = bubbleCorners.mainRadius
|
|
let smallRadius: CGFloat = (bubbleCorners.mergeBubbleCorners && largeRadius >= 10.0) ? bubbleCorners.auxiliaryRadius : bubbleCorners.mainRadius
|
|
let size: CGSize
|
|
if case .middle = position {
|
|
size = CGSize(width: smallRadius + smallRadius, height: smallRadius + smallRadius)
|
|
} else {
|
|
size = CGSize(width: largeRadius + largeRadius, height: largeRadius + largeRadius)
|
|
}
|
|
return generateImage(size, contextGenerator: { size, context in
|
|
context.clear(CGRect(origin: CGPoint(), size: size))
|
|
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
|
|
if case .bottomRight = position {
|
|
context.scaleBy(x: -1.0, y: -1.0)
|
|
} else {
|
|
context.scaleBy(x: 1.0, y: -1.0)
|
|
}
|
|
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
|
|
context.setBlendMode(.copy)
|
|
var effectiveStrokeColor: UIColor?
|
|
var strokeAlpha: CGFloat = 0.0
|
|
strokeColor.getRed(nil, green: nil, blue: nil, alpha: &strokeAlpha)
|
|
if !strokeAlpha.isZero {
|
|
effectiveStrokeColor = strokeColor
|
|
}
|
|
context.setFillColor(color.cgColor)
|
|
let lineWidth: CGFloat = 1.0
|
|
let halfLineWidth = lineWidth / 2.0
|
|
if let effectiveStrokeColor = effectiveStrokeColor {
|
|
context.setStrokeColor(effectiveStrokeColor.cgColor)
|
|
context.setLineWidth(lineWidth)
|
|
}
|
|
switch position {
|
|
case .middle:
|
|
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
|
if effectiveStrokeColor != nil {
|
|
context.setBlendMode(.normal)
|
|
context.strokeEllipse(in: CGRect(origin: CGPoint(x: halfLineWidth, y: halfLineWidth), size: CGSize(width: size.width - lineWidth, height: size.height - lineWidth)))
|
|
context.setBlendMode(.copy)
|
|
}
|
|
case .bottomLeft, .bottomRight:
|
|
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
|
context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: smallRadius + smallRadius, height: smallRadius + smallRadius)))
|
|
context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - smallRadius - smallRadius, y: 0.0), size: CGSize(width: smallRadius + smallRadius, height: smallRadius + smallRadius)))
|
|
context.fill(CGRect(origin: CGPoint(x: smallRadius, y: 0.0), size: CGSize(width: size.width - smallRadius - smallRadius, height: smallRadius + smallRadius)))
|
|
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
|
context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: smallRadius + smallRadius, height: smallRadius + smallRadius)))
|
|
context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - smallRadius - smallRadius, y: 0.0), size: CGSize(width: smallRadius + smallRadius, height: smallRadius + smallRadius)))
|
|
context.fill(CGRect(origin: CGPoint(x: smallRadius, y: 0.0), size: CGSize(width: size.width - smallRadius - smallRadius, height: smallRadius + smallRadius)))
|
|
context.fill(CGRect(origin: CGPoint(x: 0.0, y: smallRadius), size: CGSize(width: size.width, height: size.height - largeRadius - smallRadius)))
|
|
context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - smallRadius - smallRadius, y: size.height - smallRadius - smallRadius), size: CGSize(width: smallRadius + smallRadius, height: smallRadius + smallRadius)))
|
|
context.fill(CGRect(origin: CGPoint(x: largeRadius, y: size.height - largeRadius - largeRadius), size: CGSize(width: size.width - smallRadius - largeRadius, height: largeRadius + largeRadius)))
|
|
context.fill(CGRect(origin: CGPoint(x: size.width - smallRadius, y: size.height - largeRadius), size: CGSize(width: smallRadius, height: largeRadius - smallRadius)))
|
|
if effectiveStrokeColor != nil {
|
|
context.setBlendMode(.normal)
|
|
context.beginPath()
|
|
context.move(to: CGPoint(x: halfLineWidth, y: smallRadius + halfLineWidth))
|
|
context.addArc(tangent1End: CGPoint(x: halfLineWidth, y: halfLineWidth), tangent2End: CGPoint(x: halfLineWidth + smallRadius, y: halfLineWidth), radius: smallRadius)
|
|
context.addLine(to: CGPoint(x: size.width - smallRadius, y: halfLineWidth))
|
|
context.addArc(tangent1End: CGPoint(x: size.width - halfLineWidth, y: halfLineWidth), tangent2End: CGPoint(x: size.width - halfLineWidth, y: halfLineWidth + smallRadius), radius: smallRadius)
|
|
context.addLine(to: CGPoint(x: size.width - halfLineWidth, y: size.height - halfLineWidth - smallRadius))
|
|
context.addArc(tangent1End: CGPoint(x: size.width - halfLineWidth, y: size.height - halfLineWidth), tangent2End: CGPoint(x: size.width - halfLineWidth - smallRadius, y: size.height - halfLineWidth), radius: smallRadius)
|
|
context.addLine(to: CGPoint(x: halfLineWidth + largeRadius, y: size.height - halfLineWidth))
|
|
context.addArc(tangent1End: CGPoint(x: halfLineWidth, y: size.height - halfLineWidth), tangent2End: CGPoint(x: halfLineWidth, y: size.height - halfLineWidth - largeRadius), radius: largeRadius)
|
|
|
|
context.closePath()
|
|
context.strokePath()
|
|
context.setBlendMode(.copy)
|
|
}
|
|
case .bottomSingle:
|
|
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
|
context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: smallRadius + smallRadius, height: smallRadius + smallRadius)))
|
|
context.fillEllipse(in: CGRect(origin: CGPoint(x: size.width - smallRadius - smallRadius, y: 0.0), size: CGSize(width: smallRadius + smallRadius, height: smallRadius + smallRadius)))
|
|
context.fill(CGRect(origin: CGPoint(x: smallRadius, y: 0.0), size: CGSize(width: size.width - smallRadius - smallRadius, height: smallRadius + smallRadius)))
|
|
context.fill(CGRect(origin: CGPoint(x: 0.0, y: smallRadius), size: CGSize(width: size.width, height: size.height - largeRadius - smallRadius)))
|
|
|
|
if effectiveStrokeColor != nil {
|
|
context.setBlendMode(.normal)
|
|
context.beginPath()
|
|
context.move(to: CGPoint(x: halfLineWidth, y: smallRadius + halfLineWidth))
|
|
context.addArc(tangent1End: CGPoint(x: halfLineWidth, y: halfLineWidth), tangent2End: CGPoint(x: halfLineWidth + smallRadius, y: halfLineWidth), radius: smallRadius)
|
|
context.addLine(to: CGPoint(x: size.width - smallRadius, y: halfLineWidth))
|
|
context.addArc(tangent1End: CGPoint(x: size.width - halfLineWidth, y: halfLineWidth), tangent2End: CGPoint(x: size.width - halfLineWidth, y: halfLineWidth + smallRadius), radius: smallRadius)
|
|
context.addLine(to: CGPoint(x: size.width - halfLineWidth, y: size.height - halfLineWidth - largeRadius))
|
|
context.addArc(tangent1End: CGPoint(x: size.width - halfLineWidth, y: size.height - halfLineWidth), tangent2End: CGPoint(x: size.width - halfLineWidth - largeRadius, y: size.height - halfLineWidth), radius: largeRadius)
|
|
context.addLine(to: CGPoint(x: halfLineWidth + largeRadius, y: size.height - halfLineWidth))
|
|
context.addArc(tangent1End: CGPoint(x: halfLineWidth, y: size.height - halfLineWidth), tangent2End: CGPoint(x: halfLineWidth, y: size.height - halfLineWidth - largeRadius), radius: largeRadius)
|
|
|
|
context.closePath()
|
|
context.strokePath()
|
|
context.setBlendMode(.copy)
|
|
}
|
|
}
|
|
})!.stretchableImage(withLeftCapWidth: Int(size.width / 2.0), topCapHeight: Int(size.height / 2.0))
|
|
}
|