-
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Support for dynamic username coloring (#152)
* dynamic username coloring and increased contrast support * files in .pbxproj
- Loading branch information
Showing
6 changed files
with
296 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
// CGFloat+Convertions.swift | ||
|
||
import Foundation | ||
|
||
extension CGFloat { | ||
/// clamp the supplied value between a min and max | ||
/// - Parameter min: The min value | ||
/// - Parameter max: The max value | ||
func clamp(min: CGFloat, max: CGFloat) -> CGFloat { | ||
if self < min { | ||
return min | ||
} else if self > max { | ||
return max | ||
} else { | ||
return self | ||
} | ||
} | ||
|
||
/// If colour value is less than 1, add 1 to it. If temp colour value is greater than 1, substract 1 from it | ||
func convertToColourChannel() -> CGFloat { | ||
let min: CGFloat = 0 | ||
let max: CGFloat = 1 | ||
let modifier: CGFloat = 1 | ||
if self < min { | ||
return self + modifier | ||
} else if self > max { | ||
return self - max | ||
} else { | ||
return self | ||
} | ||
} | ||
|
||
/// Formula to convert the calculated colour from colour multipliers | ||
/// - Parameter temp1: Temp variable one (calculated from luminosity) | ||
/// - Parameter temp2: Temp variable two (calcualted from temp1 and luminosity) | ||
func convertToRGB(temp1: CGFloat, temp2: CGFloat) -> CGFloat { | ||
if 6 * self < 1 { | ||
return temp2 + (temp1 - temp2) * 6 * self | ||
} else if 2 * self < 1 { | ||
return temp1 | ||
} else if 3 * self < 2 { | ||
return temp2 + (temp1 - temp2) * (0.666 - self) * 6 | ||
} else { | ||
return temp2 | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
// Color+ContrastRatio.swift | ||
|
||
import SwiftUI | ||
|
||
// Source: | ||
// https://stackoverflow.com/questions/ | ||
// 42355778/how-to-compute-color-contrast-ratio-between-two-uicolor-instances | ||
extension Color { | ||
// swiftlint:disable large_tuple | ||
var components: (red: CGFloat, green: CGFloat, blue: CGFloat, opacity: CGFloat) { | ||
let components = self.cgColor?.components ?? [0, 0, 0, 0] | ||
return (components[0], components[1], components[2], components[3]) | ||
} | ||
|
||
func contrastRatio() -> Double { | ||
let luminance1 = self.luminance() | ||
let luminance2 = Color("MessageFromRecepientColor").luminance() | ||
|
||
let luminanceDarker = min(luminance1, luminance2) | ||
let luminanceLighter = max(luminance1, luminance2) | ||
|
||
let contrast = (luminanceLighter + 0.05) / (luminanceDarker + 0.05) | ||
|
||
return contrast | ||
} | ||
|
||
func luminance() -> Double { | ||
func adjust(colorComponent: CGFloat) -> CGFloat { | ||
return (colorComponent < 0.04045) ? (colorComponent / 12.92) : pow((colorComponent + 0.055) / 1.055, 2.4) | ||
} | ||
|
||
let components = self.components | ||
|
||
return 0.2126 * adjust( | ||
colorComponent: components.red) + 0.7152 * adjust( | ||
colorComponent: components.green) + 0.0722 * adjust( | ||
colorComponent: components.blue) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
// Color+DynamicUserID.swift | ||
|
||
import SwiftUI | ||
import Backend | ||
|
||
extension Color { | ||
init?(from userId: Int64) async throws { | ||
// Source: | ||
// https://www.hackingwithswift.com/example-code/media/ | ||
// how-to-read-the-average-color-of-a-uiimage-using-ciareaaverage | ||
let user = try await TdApi.shared.getUser(userId: userId) | ||
|
||
guard let profilePhoto = user.profilePhoto else { return nil } | ||
guard let photo = profilePhoto.minithumbnail else { return nil } | ||
guard let inputImage = CIImage(data: photo.data) else { return nil } | ||
|
||
let extentVector = CIVector( | ||
x: inputImage.extent.origin.x, | ||
y: inputImage.extent.origin.y, | ||
z: inputImage.extent.size.width, | ||
w: inputImage.extent.size.height | ||
) | ||
|
||
guard let filter = CIFilter( | ||
name: "CIAreaAverage", | ||
parameters: [ | ||
kCIInputImageKey: inputImage, | ||
kCIInputExtentKey: extentVector | ||
]) else { return nil } | ||
guard let outputImage = filter.outputImage else { return nil } | ||
|
||
var bitmap = [UInt8](repeating: 0, count: 4) | ||
let context = CIContext(options: [.workingColorSpace: kCFNull as Any]) | ||
|
||
context.render( | ||
outputImage, | ||
toBitmap: &bitmap, | ||
rowBytes: 4, | ||
bounds: CGRect(x: 0, y: 0, width: 1, height: 1), | ||
format: .RGBA8, | ||
colorSpace: nil | ||
) | ||
|
||
self.init( | ||
red: CGFloat(bitmap[0]) / 255, | ||
green: CGFloat(bitmap[1]) / 255, | ||
blue: CGFloat(bitmap[2]) / 255, | ||
opacity: 1 | ||
) | ||
} | ||
|
||
// Source: | ||
// https://medium.com/trinity-mirror-digital/adjusting-uicolor-luminosity-in-swift-4168e3c4cdf1 | ||
// swiftlint:disable function_body_length cyclomatic_complexity | ||
func withLuminosity(_ colorSchemeContrast: ColorSchemeContrast = .standard) -> Color { | ||
// TODO: calculate newLuminosity | ||
// depending on the foreground color of a message bubble | ||
// Color.contrastRatio() may help | ||
let newLuminosity: CGFloat | ||
switch colorSchemeContrast { | ||
case .standard: | ||
newLuminosity = 0.6 | ||
case .increased: | ||
newLuminosity = 0.8 | ||
@unknown default: | ||
newLuminosity = 0.6 | ||
} | ||
|
||
let nsColor = NSColor(self) | ||
guard let coreColour = CIColor(color: nsColor) else { return self } | ||
var red = coreColour.red | ||
var green = coreColour.green | ||
var blue = coreColour.blue | ||
let alpha = coreColour.alpha | ||
|
||
red = red.clamp(min: 0, max: 1) | ||
green = green.clamp(min: 0, max: 1) | ||
blue = blue.clamp(min: 0, max: 1) | ||
|
||
guard let minRGB = [red, green, blue].min(), | ||
let maxRGB = [red, green, blue].max() else { return self } | ||
|
||
var luminosity = (minRGB + maxRGB) / 2 | ||
|
||
var saturation: CGFloat = 0 | ||
|
||
if luminosity <= 0.5 { | ||
saturation = (maxRGB - minRGB)/(maxRGB + minRGB) | ||
} else if luminosity > 0.5 { | ||
saturation = (maxRGB - minRGB)/(2.0 - maxRGB - minRGB) | ||
} | ||
|
||
var hue: CGFloat = 0 | ||
if red == maxRGB { | ||
hue = (green - blue) / (maxRGB - minRGB) | ||
} else if green == maxRGB { | ||
hue = 2.0 + ((blue - red) / (maxRGB - minRGB)) | ||
} else if blue == maxRGB { | ||
hue = 4.0 + ((red - green) / (maxRGB - minRGB)) | ||
} | ||
|
||
if hue < 0 { | ||
hue += 360 | ||
} else { | ||
hue *= 60 | ||
} | ||
|
||
luminosity = newLuminosity | ||
|
||
if saturation == 0 { | ||
return Color(red: 1.0 * luminosity, green: 1.0 * luminosity, blue: 1.0 * luminosity, opacity: alpha) | ||
} | ||
|
||
var temporaryVariableOne: CGFloat = 0 | ||
if luminosity < 0.5 { | ||
temporaryVariableOne = luminosity * (1 + saturation) | ||
} else { | ||
temporaryVariableOne = luminosity + saturation - luminosity * saturation | ||
} | ||
|
||
let temporaryVariableTwo = 2 * luminosity - temporaryVariableOne | ||
|
||
let convertedHue = hue / 360 | ||
|
||
let tempRed = (convertedHue + 0.333).convertToColourChannel() | ||
let tempGreen = convertedHue.convertToColourChannel() | ||
let tempBlue = (convertedHue - 0.333).convertToColourChannel() | ||
|
||
let newRed = tempRed.convertToRGB(temp1: temporaryVariableOne, temp2: temporaryVariableTwo) | ||
let newGreen = tempGreen.convertToRGB(temp1: temporaryVariableOne, temp2: temporaryVariableTwo) | ||
let newBlue = tempBlue.convertToRGB(temp1: temporaryVariableOne, temp2: temporaryVariableTwo) | ||
|
||
return Color(red: newRed, green: newGreen, blue: newBlue, opacity: alpha) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.