mirror of
https://github.com/HaishinKit/HaishinKit.swift.git
synced 2026-05-07 20:12:28 +00:00
de6def6aae
* Rename VideoTrackScreenObject -> VideoScreenObject. * ScreenObjectSnapshot.frame -> ScreenObjectSnapshot.size * Support data scheme image source for ImageScreenObject. * Add Test.
229 lines
6.7 KiB
Swift
229 lines
6.7 KiB
Swift
#if canImport(AppKit)
|
|
import AppKit
|
|
|
|
extension NSColor {
|
|
fileprivate convenience init?(hex: String) {
|
|
var hex = hex
|
|
if hex.hasPrefix("#") {
|
|
hex.removeFirst()
|
|
}
|
|
|
|
guard hex.count == 8,
|
|
let value = UInt32(hex, radix: 16) else {
|
|
return nil
|
|
}
|
|
|
|
let a = CGFloat((value & 0xFF00_0000) >> 24) / 255.0
|
|
let r = CGFloat((value & 0x00FF_0000) >> 16) / 255.0
|
|
let g = CGFloat((value & 0x0000_FF00) >> 8) / 255.0
|
|
let b = CGFloat(value & 0x0000_00FF) / 255.0
|
|
|
|
self.init(red: r, green: g, blue: b, alpha: a)
|
|
}
|
|
|
|
fileprivate func toHexARGB() -> String? {
|
|
var r: CGFloat = 0
|
|
var g: CGFloat = 0
|
|
var b: CGFloat = 0
|
|
var a: CGFloat = 0
|
|
|
|
self.getRed(&r, green: &g, blue: &b, alpha: &a)
|
|
|
|
let ri = Int(round(r * 255))
|
|
let gi = Int(round(g * 255))
|
|
let bi = Int(round(b * 255))
|
|
let ai = Int(round(a * 255))
|
|
|
|
return String(format: "#%02X%02X%02X%02X", ai, ri, gi, bi)
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if canImport(UIKit)
|
|
import UIKit
|
|
|
|
extension UIColor {
|
|
fileprivate convenience init?(hex: String) {
|
|
var hex = hex
|
|
if hex.hasPrefix("#") {
|
|
hex.removeFirst()
|
|
}
|
|
|
|
guard hex.count == 8,
|
|
let value = UInt32(hex, radix: 16) else {
|
|
return nil
|
|
}
|
|
|
|
let a = CGFloat((value & 0xFF00_0000) >> 24) / 255.0
|
|
let r = CGFloat((value & 0x00FF_0000) >> 16) / 255.0
|
|
let g = CGFloat((value & 0x0000_FF00) >> 8) / 255.0
|
|
let b = CGFloat(value & 0x0000_00FF) / 255.0
|
|
|
|
self.init(red: r, green: g, blue: b, alpha: a)
|
|
}
|
|
|
|
fileprivate func toHexRGBA() -> String? {
|
|
var r: CGFloat = 0
|
|
var g: CGFloat = 0
|
|
var b: CGFloat = 0
|
|
var a: CGFloat = 0
|
|
|
|
guard self.getRed(&r, green: &g, blue: &b, alpha: &a) else {
|
|
return nil
|
|
}
|
|
|
|
let ri = Int(round(r * 255))
|
|
let gi = Int(round(g * 255))
|
|
let bi = Int(round(b * 255))
|
|
let ai = Int(round(a * 255))
|
|
|
|
return String(format: "#%02X%02X%02X%02X", ai, ri, gi, bi)
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/// An object that manages offscreen rendering a text source.
|
|
public final class TextScreenObject: ScreenObject {
|
|
public static let type: String = "text"
|
|
|
|
/// Specifies the text value.
|
|
public var string: String = "" {
|
|
didSet {
|
|
guard string != oldValue else {
|
|
return
|
|
}
|
|
invalidateLayout()
|
|
}
|
|
}
|
|
|
|
#if os(macOS)
|
|
/// Specifies the attributes for strings.
|
|
public var attributes: [NSAttributedString.Key: Any]? = [
|
|
.font: NSFont.boldSystemFont(ofSize: 32),
|
|
.foregroundColor: NSColor.white
|
|
] {
|
|
didSet {
|
|
invalidateLayout()
|
|
}
|
|
}
|
|
|
|
override public var elements: [String: String] {
|
|
get {
|
|
var size: String?
|
|
if let font = attributes?[.foregroundColor] as? NSFont {
|
|
size = font.pointSize.description
|
|
}
|
|
var color: String?
|
|
if let foregroundColor = attributes?[.foregroundColor] as? NSColor {
|
|
color = foregroundColor.toHexARGB()
|
|
}
|
|
return [
|
|
"value": string,
|
|
"fontSize": size ?? "32",
|
|
"color": color ?? "#FFFFFFFF"
|
|
]
|
|
}
|
|
set {
|
|
string = newValue["value"] ?? ""
|
|
if let size = Double(newValue["fontSize"] ?? "32.0") {
|
|
attributes?[.font] = NSFont.boldSystemFont(ofSize: size)
|
|
}
|
|
if let color = NSColor(hex: newValue["color"] ?? "#FFFFFFFF") {
|
|
attributes?[.foregroundColor] = color
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
/// Specifies the attributes for strings.
|
|
public var attributes: [NSAttributedString.Key: Any]? = [
|
|
.font: UIFont.boldSystemFont(ofSize: 32),
|
|
.foregroundColor: UIColor.white
|
|
] {
|
|
didSet {
|
|
invalidateLayout()
|
|
}
|
|
}
|
|
|
|
override public var elements: [String: String] {
|
|
get {
|
|
var size: String?
|
|
if let font = attributes?[.foregroundColor] as? UIFont {
|
|
size = font.pointSize.description
|
|
}
|
|
var color: String?
|
|
if let foregroundColor = attributes?[.foregroundColor] as? UIColor {
|
|
color = foregroundColor.toHexRGBA()
|
|
}
|
|
return [
|
|
"value": string,
|
|
"fontSize": size ?? "32",
|
|
"color": color ?? "#FFFFFFFF"
|
|
]
|
|
}
|
|
set {
|
|
string = newValue["value"] ?? ""
|
|
if let size = Double(newValue["fontSize"] ?? "32.0") {
|
|
attributes?[.font] = UIFont.boldSystemFont(ofSize: size)
|
|
}
|
|
if let color = UIColor(hex: newValue["color"] ?? "#FFFFFFFF") {
|
|
attributes?[.foregroundColor] = color
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
override public var bounds: CGRect {
|
|
didSet {
|
|
guard bounds != oldValue else {
|
|
return
|
|
}
|
|
context = CGContext(
|
|
data: nil,
|
|
width: Int(bounds.width),
|
|
height: Int(bounds.height),
|
|
bitsPerComponent: 8,
|
|
bytesPerRow: Int(bounds.width) * 4,
|
|
space: CGColorSpaceCreateDeviceRGB(),
|
|
bitmapInfo: CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedFirst.rawValue).rawValue
|
|
)
|
|
}
|
|
}
|
|
|
|
private var context: CGContext?
|
|
private var framesetter: CTFramesetter?
|
|
|
|
override public func makeBounds(_ size: CGSize) -> CGRect {
|
|
guard !string.isEmpty else {
|
|
self.framesetter = nil
|
|
return .zero
|
|
}
|
|
let bounds = super.makeBounds(size)
|
|
let attributedString = NSAttributedString(string: string, attributes: attributes)
|
|
let framesetter = CTFramesetterCreateWithAttributedString(attributedString)
|
|
let frameSize = CTFramesetterSuggestFrameSizeWithConstraints(
|
|
framesetter,
|
|
.init(),
|
|
nil,
|
|
bounds.size,
|
|
nil
|
|
)
|
|
self.framesetter = framesetter
|
|
return super.makeBounds(frameSize)
|
|
}
|
|
|
|
override public func makeImage(_ renderer: some ScreenRenderer) -> CIImage? {
|
|
guard let context, let framesetter else {
|
|
return nil
|
|
}
|
|
let path = CGPath(rect: .init(origin: .zero, size: bounds.size), transform: nil)
|
|
let frame = CTFramesetterCreateFrame(framesetter, .init(), path, nil)
|
|
context.clear(context.boundingBoxOfPath)
|
|
CTFrameDraw(frame, context)
|
|
if let cgImage = context.makeImage() {
|
|
return CIImage(cgImage: cgImage)
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
}
|