// // SportsIconImageGenerator.swift // SportsTime // // Generates a 1024x1024 PNG with randomly scattered sports icons. // import SwiftUI import UIKit struct SportsIconImageGenerator { /// All sports icons used in the animated background private static let sportsIcons: [String] = [ // Sports balls "football.fill", "basketball.fill", "baseball.fill", "hockey.puck.fill", "soccerball", "tennisball.fill", "volleyball.fill", // Sports figures "figure.run", "figure.baseball", "figure.basketball", "figure.hockey", "figure.soccer", // Venues/events "sportscourt.fill", "trophy.fill", "ticket.fill", // Travel/navigation "mappin.circle.fill", "car.fill", "map.fill", "flag.checkered" ] /// Generates a 1024x1024 PNG image with randomly scattered sports icons /// - Returns: A UIImage with transparent background containing the icons (exactly 1024x1024 pixels) static func generateImage() -> UIImage { let size = CGSize(width: 1024, height: 1024) let maxIconSize: CGFloat = 150 let minIconSize: CGFloat = 40 let minSpacing: CGFloat = 80 // Minimum distance between icon centers // Use scale 1.0 to ensure exact pixel dimensions (not retina scaled) let format = UIGraphicsImageRendererFormat() format.scale = 1.0 let renderer = UIGraphicsImageRenderer(size: size, format: format) // Generate well-distributed positions using Poisson disk sampling let positions = generateScatteredPositions( in: size, minSpacing: minSpacing, targetCount: 45 ) let image = renderer.image { context in // Clear background (transparent) UIColor.clear.setFill() context.fill(CGRect(origin: .zero, size: size)) // Shuffle icons to randomize which icon appears where let shuffledIcons = sportsIcons.shuffled() // Draw icons at scattered positions for (index, position) in positions.enumerated() { let iconName = shuffledIcons[index % shuffledIcons.count] let targetSize = CGFloat.random(in: minIconSize...maxIconSize) let rotation = CGFloat.random(in: -30...30) * .pi / 180 // Create SF Symbol image at a large point size for quality let config = UIImage.SymbolConfiguration(pointSize: 100, weight: .regular) if let symbolImage = UIImage(systemName: iconName, withConfiguration: config) { // Tint with warm orange let tintedImage = symbolImage.withTintColor( UIColor(Theme.warmOrange), renderingMode: .alwaysOriginal ) // Calculate proportional size maintaining aspect ratio let originalSize = tintedImage.size let aspectRatio = originalSize.width / originalSize.height let drawSize: CGSize if aspectRatio > 1 { // Wider than tall - constrain by width drawSize = CGSize(width: targetSize, height: targetSize / aspectRatio) } else { // Taller than wide - constrain by height drawSize = CGSize(width: targetSize * aspectRatio, height: targetSize) } // Save graphics state for rotation context.cgContext.saveGState() // Position at the scattered point context.cgContext.translateBy(x: position.x, y: position.y) context.cgContext.rotate(by: rotation) // Draw the icon centered at origin with correct aspect ratio let drawRect = CGRect( x: -drawSize.width / 2, y: -drawSize.height / 2, width: drawSize.width, height: drawSize.height ) tintedImage.draw(in: drawRect) context.cgContext.restoreGState() } } } return image } /// Generates well-distributed points using a grid-based approach with jitter /// This ensures icons are scattered across the entire image, not clumped together private static func generateScatteredPositions( in size: CGSize, minSpacing: CGFloat, targetCount: Int ) -> [CGPoint] { var positions: [CGPoint] = [] // Calculate grid dimensions to achieve roughly targetCount points let gridSize = Int(ceil(sqrt(Double(targetCount)))) let cellWidth = size.width / CGFloat(gridSize) let cellHeight = size.height / CGFloat(gridSize) // Margin from edges let margin: CGFloat = 40 for row in 0.. Data? { let image = generateImage() return image.pngData() } } // MARK: - SwiftUI View for generating and sharing struct SportsIconImageGeneratorView: View { @State private var generatedImage: UIImage? @State private var isGenerating = false @State private var showShareSheet = false var body: some View { VStack(spacing: 16) { // Preview of generated image if let image = generatedImage { Image(uiImage: image) .resizable() .aspectRatio(contentMode: .fit) .frame(maxWidth: 300, maxHeight: 300) .background( // Checkerboard pattern to show transparency CheckerboardPattern() ) .clipShape(RoundedRectangle(cornerRadius: 12)) .overlay( RoundedRectangle(cornerRadius: 12) .stroke(Color.secondary.opacity(0.3), lineWidth: 1) ) } // Generate button Button { generateNewImage() } label: { HStack { if isGenerating { ProgressView() .tint(.white) } else { Image(systemName: "dice.fill") } Text(generatedImage == nil ? "Generate Image" : "Regenerate") } .frame(maxWidth: .infinity) .padding() .background(Theme.warmOrange) .foregroundStyle(.white) .clipShape(RoundedRectangle(cornerRadius: 12)) } .disabled(isGenerating) // Share button (only visible when image exists) if generatedImage != nil { ShareLink( item: Image(uiImage: generatedImage!), preview: SharePreview("Sports Icons", image: Image(uiImage: generatedImage!)) ) { HStack { Image(systemName: "square.and.arrow.up") Text("Share via AirDrop") } .frame(maxWidth: .infinity) .padding() .background(Color.blue) .foregroundStyle(.white) .clipShape(RoundedRectangle(cornerRadius: 12)) } } } .padding() } private func generateNewImage() { isGenerating = true // Generate on background thread to avoid UI freeze Task { let image = await Task.detached(priority: .userInitiated) { SportsIconImageGenerator.generateImage() }.value withAnimation { generatedImage = image isGenerating = false } } } } // MARK: - Checkerboard Pattern for transparency preview private struct CheckerboardPattern: View { var body: some View { Canvas { context, size in let tileSize: CGFloat = 10 let rows = Int(ceil(size.height / tileSize)) let cols = Int(ceil(size.width / tileSize)) for row in 0..