aboutsummaryrefslogtreecommitdiff
path: root/Hotline/Utility
diff options
context:
space:
mode:
authorDustin Mierau <dustin@mierau.me>2025-11-07 10:19:42 -0800
committerDustin Mierau <dustin@mierau.me>2025-11-07 10:19:42 -0800
commit87f08cf60a5d7c1cf618463916cbac4dab88e0f8 (patch)
treec15a94443801beff5fcb8ac5d5dbc8276726ee39 /Hotline/Utility
parentbcf36ef614aa46ae3d8e714add470f749fdf3714 (diff)
Massive refactor of transfer cients (new folder upload implementation), brand new async version of NetSocket, and a rewritten Hotline view model. New cross-server transfers UI.
Diffstat (limited to 'Hotline/Utility')
-rw-r--r--Hotline/Utility/ColorArt.swift147
-rw-r--r--Hotline/Utility/SwiftUIExtensions.swift25
2 files changed, 94 insertions, 78 deletions
diff --git a/Hotline/Utility/ColorArt.swift b/Hotline/Utility/ColorArt.swift
index 1f99f0f..93889a3 100644
--- a/Hotline/Utility/ColorArt.swift
+++ b/Hotline/Utility/ColorArt.swift
@@ -1,7 +1,11 @@
+// Swift translation and modernization
+// by Dustin Mierau
+//
+// of:
+//
// ColorArt.swift
// SLColorArt by Panic Inc.
-// Swift translation by Dustin Mierau
//
// Copyright (C) 2012 Panic Inc. Code by Wade Cosgrove. All rights reserved.
//
@@ -32,12 +36,14 @@ import SwiftUI
fileprivate let kColorThresholdMinimumPercentage: CGFloat = 0.001
+// ColorArt.analyze(image: img) -> ColorArt?
+
struct ColorArt: Equatable {
let backgroundColor: NSColor
let primaryColor: NSColor
let secondaryColor: NSColor
let detailColor: NSColor
- let scaledImage: NSImage
+// let scaledImage: NSImage
static func == (lhs: ColorArt, rhs: ColorArt) -> Bool {
return lhs.backgroundColor == rhs.backgroundColor &&
@@ -45,57 +51,80 @@ struct ColorArt: Equatable {
lhs.secondaryColor == rhs.secondaryColor &&
lhs.detailColor == rhs.detailColor
}
+
+ static func analyze(image: NSImage) -> ColorArt? {
+ print("ColorArt.analyze: Starting, image size: \(image.size)")
+ // Scale image to a reasonable size for analysis
+ // This is important because:
+ // 1. Makes analysis faster (fewer pixels)
+ // 2. Normalizes weird image dimensions
+ // 3. Ensures CGImage conversion succeeds
+ print("ColorArt.analyze: Calling scaleImage...")
+ let finalImage = Self.scaleImage(image, size: NSSize(width: 100, height: 100))
+ print("ColorArt.analyze: scaleImage returned, scaled size: \(finalImage.size)")
- init?(image: NSImage, scaledSize: NSSize = .zero) {
- let finalImage = Self.scaleImage(image, size: scaledSize)
- self.scaledImage = finalImage
-
guard let colors = Self.analyzeImage(finalImage) else {
+ print("ColorArt.analyze: failed with no colors")
return nil
}
- self.backgroundColor = colors.background
- self.primaryColor = colors.primary
- self.secondaryColor = colors.secondary
- self.detailColor = colors.detail
+ print("ColorArt.analyze: returning colors", colors)
+
+ return ColorArt(backgroundColor: colors.background,
+ primaryColor: colors.primary,
+ secondaryColor: colors.secondary,
+ detailColor: colors.detail)
}
// MARK: - Image Scaling
private static func scaleImage(_ image: NSImage, size scaledSize: NSSize) -> NSImage {
- let imageSize = image.size
- let squareImage = NSImage(size: NSSize(width: imageSize.width, height: imageSize.width))
- var drawRect: NSRect
-
- // Make the image square
- if imageSize.height > imageSize.width {
- drawRect = NSRect(x: 0, y: imageSize.height - imageSize.width, width: imageSize.width, height: imageSize.width)
- } else {
- drawRect = NSRect(x: 0, y: 0, width: imageSize.height, height: imageSize.height)
+ print("ColorArt.scaleImage: Entered, input: \(image.size), target: \(scaledSize)")
+ // Get CGImage directly without using lockFocus
+ print("ColorArt.scaleImage: Getting CGImage...")
+ guard let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil) else {
+ print("ColorArt.scaleImage: Failed to get CGImage, returning original")
+ return image
}
-
+ print("ColorArt.scaleImage: Got CGImage")
+
+ let imageSize = image.size
+ let squareSize = min(imageSize.width, imageSize.height)
+
// Use native square size if passed zero size
- let finalScaledSize = scaledSize == .zero ? drawRect.size : scaledSize
- let scaledImage = NSImage(size: finalScaledSize)
-
- squareImage.lockFocus()
- image.draw(in: NSRect(x: 0, y: 0, width: imageSize.width, height: imageSize.width), from: drawRect, operation: .sourceOver, fraction: 1.0)
- squareImage.unlockFocus()
-
- // Scale the image to the desired size
- scaledImage.lockFocus()
- squareImage.draw(in: NSRect(x: 0, y: 0, width: finalScaledSize.width, height: finalScaledSize.height), from: .zero, operation: .sourceOver, fraction: 1.0)
- scaledImage.unlockFocus()
-
- // Convert back to readable bitmap data
- guard let cgImage = scaledImage.cgImage(forProposedRect: nil, context: nil, hints: nil) else {
- return scaledImage
+ let finalScaledSize = scaledSize == .zero ? NSSize(width: squareSize, height: squareSize) : scaledSize
+
+ // Create bitmap context for drawing
+ let width = Int(finalScaledSize.width)
+ let height = Int(finalScaledSize.height)
+ let colorSpace = CGColorSpaceCreateDeviceRGB()
+ let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
+
+ guard let context = CGContext(
+ data: nil,
+ width: width,
+ height: height,
+ bitsPerComponent: 8,
+ bytesPerRow: width * 4,
+ space: colorSpace,
+ bitmapInfo: bitmapInfo.rawValue
+ ) else {
+ return image
}
-
- let bitmapRep = NSBitmapImageRep(cgImage: cgImage)
- let finalImage = NSImage(size: scaledImage.size)
+
+ // Draw the image scaled
+ context.interpolationQuality = .high
+ context.draw(cgImage, in: CGRect(x: 0, y: 0, width: finalScaledSize.width, height: finalScaledSize.height))
+
+ // Create NSImage from context
+ guard let scaledCGImage = context.makeImage() else {
+ return image
+ }
+
+ let bitmapRep = NSBitmapImageRep(cgImage: scaledCGImage)
+ let finalImage = NSImage(size: finalScaledSize)
finalImage.addRepresentation(bitmapRep)
-
+
return finalImage
}
@@ -120,33 +149,45 @@ struct ColorArt: Equatable {
if primaryColor == nil {
primaryColor = darkBackground ? .white : .black
}
-
+
if secondaryColor == nil {
secondaryColor = darkBackground ? .white : .black
}
-
+
if detailColor == nil {
detailColor = darkBackground ? .white : .black
}
-
- return (backgroundColor, primaryColor!, secondaryColor!, detailColor!)
+
+ // Convert all colors to calibrated RGB color space for consistency
+ // This ensures all colors are in the same color space and prevents
+ // any color space conversion issues when used in SwiftUI
+ let rgbColorSpace = NSColorSpace.genericRGB
+ let finalBackground = backgroundColor.usingColorSpace(rgbColorSpace) ?? backgroundColor
+ let finalPrimary = primaryColor!.usingColorSpace(rgbColorSpace) ?? primaryColor!
+ let finalSecondary = secondaryColor!.usingColorSpace(rgbColorSpace) ?? secondaryColor!
+ let finalDetail = detailColor!.usingColorSpace(rgbColorSpace) ?? detailColor!
+
+ return (finalBackground, finalPrimary, finalSecondary, finalDetail)
}
// MARK: - Edge Color Detection
private static func findEdgeColor(_ image: NSImage, imageColors: inout NSCountedSet?) -> NSColor? {
- guard var imageRep = image.representations.last else {
- return nil
- }
-
- if !(imageRep is NSBitmapImageRep) {
- image.lockFocus()
- imageRep = NSBitmapImageRep(focusedViewRect: NSRect(x: 0, y: 0, width: image.size.width, height: image.size.height))!
- image.unlockFocus()
+ var bitmapRep: NSBitmapImageRep?
+
+ // Try to get existing bitmap representation
+ if let existingRep = image.representations.last as? NSBitmapImageRep {
+ bitmapRep = existingRep
+ } else {
+ // Create bitmap rep from CGImage instead of using lockFocus
+ guard let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil) else {
+ return nil
+ }
+ bitmapRep = NSBitmapImageRep(cgImage: cgImage)
}
-
+
// Convert to RGB color space
- guard let bitmapRep = (imageRep as? NSBitmapImageRep)?.converting(to: .genericRGB, renderingIntent: .default) else {
+ guard let bitmapRep = bitmapRep?.converting(to: .genericRGB, renderingIntent: .default) else {
return nil
}
diff --git a/Hotline/Utility/SwiftUIExtensions.swift b/Hotline/Utility/SwiftUIExtensions.swift
index 12217b2..d0dfff4 100644
--- a/Hotline/Utility/SwiftUIExtensions.swift
+++ b/Hotline/Utility/SwiftUIExtensions.swift
@@ -6,28 +6,3 @@ extension Color {
self.init(red: Double((hex >> 16) & 0xFF) / 255.0, green: Double((hex >> 8) & 0xFF) / 255.0, blue: Double(hex & 0xFF) / 255.0, opacity: opacity)
}
}
-
-extension AttributedString {
- func setHangingIndent(firstLineHeadIndent: CGFloat = 0, otherLinesHeadIndent: CGFloat) -> AttributedString {
-// var blah = self
-
-// guard var paragraph = self.paragraphStyle else {
-// return
-// }
-
- var p = self.paragraphStyle?.mutableCopy() as? NSMutableParagraphStyle
- p?.headIndent = otherLinesHeadIndent
- p?.firstLineHeadIndent = firstLineHeadIndent
-
-// paragraph.headIndent = otherLinesHeadIndent // indent for lines 2+
-// paragraph.firstLineHeadIndent = firstLineHeadIndent // usually 0
-
- var blah = self
-
-
- blah.paragraphStyle = p
-
- return blah
- }
-}
-