mirror of
https://github.com/ichmagmaus111/ghostgram.git
synced 2026-11-03 22:27:13 +01:00
Ghostgram for iOS, rebuilt from scratch on Telegram iOS 13.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
172 lines
6.7 KiB
Swift
172 lines
6.7 KiB
Swift
import UIKit
|
|
|
|
private let walletCardTextureSize = CGSize(width: 361.0, height: 220.0)
|
|
private let walletCardTextureSourceSize = CGSize(width: 370.0, height: 220.0)
|
|
|
|
enum WalletCardTextures {
|
|
private static let cachedStars = makeStarsImage()
|
|
private static let cachedNoise = makeNoiseImage()
|
|
private static let cachedQR = makeQRImage()
|
|
|
|
static func starsImage() -> UIImage {
|
|
return self.cachedStars
|
|
}
|
|
|
|
static func noiseImage() -> UIImage {
|
|
return self.cachedNoise
|
|
}
|
|
|
|
static func qrImage() -> UIImage {
|
|
return self.cachedQR
|
|
}
|
|
|
|
private static func makeQRImage() -> UIImage {
|
|
let format = UIGraphicsImageRendererFormat()
|
|
format.preferredRange = .standard
|
|
format.scale = 8.0
|
|
format.opaque = false
|
|
return UIGraphicsImageRenderer(size: CGSize(width: 22.0, height: 22.0), format: format).image { context in
|
|
let cg = context.cgContext
|
|
cg.translateBy(x: -282.5, y: -90.0)
|
|
cg.setFillColor(UIColor.white.cgColor)
|
|
for origin in [CGPoint(x: 284.25, y: 91.75), CGPoint(x: 294.5, y: 91.75), CGPoint(x: 284.25, y: 102.0)] {
|
|
let outer = CGRect(origin: origin, size: CGSize(width: 8.25, height: 8.25))
|
|
let inner = outer.insetBy(dx: 1.375, dy: 1.375)
|
|
let path = CGMutablePath()
|
|
path.addRoundedRect(in: outer, cornerWidth: 2.4, cornerHeight: 2.4)
|
|
path.addRoundedRect(in: inner, cornerWidth: 1.1, cornerHeight: 1.1)
|
|
cg.addPath(path)
|
|
cg.fillPath(using: .evenOdd)
|
|
}
|
|
for origin in [CGPoint(x: 295.0, y: 103.0), CGPoint(x: 300.12, y: 103.0), CGPoint(x: 297.6, y: 105.3),
|
|
CGPoint(x: 295.0, y: 108.15), CGPoint(x: 300.12, y: 108.15)] {
|
|
let dot = CGRect(origin: origin, size: CGSize(width: 2.12, height: 2.1))
|
|
cg.addPath(CGPath(roundedRect: dot, cornerWidth: 0.45, cornerHeight: 0.45, transform: nil))
|
|
cg.fillPath()
|
|
}
|
|
}
|
|
}
|
|
|
|
private static func makeStarsImage() -> UIImage {
|
|
let centers: [CGPoint] = [
|
|
CGPoint(x: 87.12, y: 47.0),
|
|
CGPoint(x: 312.6, y: 156.0),
|
|
CGPoint(x: 29.72, y: 22.0),
|
|
CGPoint(x: 249.06, y: 195.0),
|
|
CGPoint(x: 121.97, y: 62.0),
|
|
CGPoint(x: 227.53, y: 138.0),
|
|
CGPoint(x: 248.03, y: 164.0),
|
|
CGPoint(x: 69.69, y: 20.0),
|
|
CGPoint(x: 127.09, y: 36.0),
|
|
CGPoint(x: 56.37, y: 67.0),
|
|
CGPoint(x: 177.0, y: 24.0),
|
|
CGPoint(x: 219.0, y: 53.0),
|
|
CGPoint(x: 284.0, y: 28.0),
|
|
CGPoint(x: 338.0, y: 48.0),
|
|
CGPoint(x: 330.0, y: 112.0),
|
|
CGPoint(x: 292.0, y: 132.0),
|
|
CGPoint(x: 181.0, y: 151.0),
|
|
CGPoint(x: 142.0, y: 172.0),
|
|
CGPoint(x: 47.0, y: 145.0),
|
|
CGPoint(x: 95.0, y: 112.0),
|
|
]
|
|
let phases: [CGFloat] = [
|
|
0.05, 0.63, 0.22, 0.87, 0.42, 0.12, 0.74, 0.33, 0.55, 0.94,
|
|
0.18, 0.69, 0.38, 0.81, 0.27, 0.58, 0.96, 0.47, 0.07, 0.76,
|
|
]
|
|
let scaleX = walletCardTextureSize.width / walletCardTextureSourceSize.width
|
|
let scaleY = walletCardTextureSize.height / walletCardTextureSourceSize.height
|
|
|
|
let format = UIGraphicsImageRendererFormat()
|
|
format.preferredRange = .standard
|
|
format.scale = 3.0
|
|
format.opaque = false
|
|
|
|
return UIGraphicsImageRenderer(size: walletCardTextureSize, format: format).image { context in
|
|
let cgContext = context.cgContext
|
|
for (index, sourceCenter) in centers.enumerated() {
|
|
let center = CGPoint(x: sourceCenter.x * scaleX, y: sourceCenter.y * scaleY)
|
|
let phase = phases[index]
|
|
cgContext.setFillColor(UIColor(red: 1.0, green: phase, blue: 0.0, alpha: 1.0).cgColor)
|
|
cgContext.addPath(self.starPath(
|
|
center: center,
|
|
outerRadius: 4.05 * min(scaleX, scaleY),
|
|
innerRadius: 1.215 * min(scaleX, scaleY)
|
|
))
|
|
cgContext.fillPath()
|
|
}
|
|
}
|
|
}
|
|
|
|
private static func starPath(center: CGPoint, outerRadius: CGFloat, innerRadius: CGFloat) -> CGPath {
|
|
let path = CGMutablePath()
|
|
for index in 0 ..< 8 {
|
|
let radius = index.isMultiple(of: 2) ? outerRadius : innerRadius
|
|
let angle = -CGFloat.pi / 2.0 + CGFloat(index) * CGFloat.pi / 4.0
|
|
let point = CGPoint(
|
|
x: center.x + radius * cos(angle),
|
|
y: center.y + radius * sin(angle)
|
|
)
|
|
if index == 0 {
|
|
path.move(to: point)
|
|
} else {
|
|
path.addLine(to: point)
|
|
}
|
|
}
|
|
path.closeSubpath()
|
|
return path
|
|
}
|
|
|
|
private static func makeNoiseImage() -> UIImage {
|
|
let dimension = 256
|
|
var seed: UInt64 = 0x2545_F491_4F6C_DD1D
|
|
|
|
func random() -> Float {
|
|
seed ^= seed << 13
|
|
seed ^= seed >> 7
|
|
seed ^= seed << 17
|
|
return Float(seed & 0xffff) / Float(0xffff)
|
|
}
|
|
|
|
let base = (0 ..< dimension * dimension).map { _ in random() }
|
|
let radius = 9
|
|
var pixels = [UInt8](repeating: 0, count: dimension * dimension)
|
|
|
|
for y in 0 ..< dimension {
|
|
for x in 0 ..< dimension {
|
|
var accumulator: Float = 0.0
|
|
for offset in -radius ... radius {
|
|
accumulator += base[y * dimension + ((x + offset + dimension) % dimension)]
|
|
}
|
|
var value = accumulator / Float(2 * radius + 1)
|
|
value = 0.5 + (value - 0.5) * 3.4
|
|
let combined = 0.68 * value + 0.32 * random()
|
|
pixels[y * dimension + x] = UInt8(max(0.0, min(255.0, combined * 255.0)))
|
|
}
|
|
}
|
|
|
|
let grayImage: CGImage = pixels.withUnsafeMutableBytes { buffer in
|
|
let context = CGContext(
|
|
data: buffer.baseAddress,
|
|
width: dimension,
|
|
height: dimension,
|
|
bitsPerComponent: 8,
|
|
bytesPerRow: dimension,
|
|
space: CGColorSpaceCreateDeviceGray(),
|
|
bitmapInfo: CGImageAlphaInfo.none.rawValue
|
|
)!
|
|
return context.makeImage()!
|
|
}
|
|
|
|
let format = UIGraphicsImageRendererFormat()
|
|
format.preferredRange = .standard
|
|
format.scale = 1.0
|
|
format.opaque = true
|
|
let size = CGSize(width: dimension, height: dimension)
|
|
|
|
return UIGraphicsImageRenderer(size: size, format: format).image { _ in
|
|
UIImage(cgImage: grayImage).draw(in: CGRect(origin: .zero, size: size))
|
|
}
|
|
}
|
|
}
|