import Foundation import UIKit /// Area-based resize used by the nyris example web app (includes upscaling small sources). func jpegForSearch(image: UIImage, maxSide: CGFloat = 1024, quality: CGFloat = 0.9) -> Data? { let aspect = image.size.width / max(image.size.height, 1) let area = maxSide * maxSide let width = max(1, (area * aspect).squareRoot().rounded()) let height = max(1, (width / max(aspect, 0.0001)).rounded()) let format = UIGraphicsImageRendererFormat.default() // default() uses the screen scale (2x/3x); the size below must be pixels. format.scale = 1 format.opaque = true let renderer = UIGraphicsImageRenderer( size: CGSize(width: width, height: height), format: format ) let rendered = renderer.image { ctx in UIColor.white.setFill() ctx.fill(CGRect(x: 0, y: 0, width: width, height: height)) image.draw(in: CGRect(x: 0, y: 0, width: width, height: height)) } return rendered.jpegData(compressionQuality: quality) } enum FindError: Error { case missingKey case http(Int) } struct FindClient { let apiKey: String let baseURL = URL(string: "https://api.nyris.io")! /// `text` is a multipart field. Do not put it in the URL query. func find(jpeg: Data?, text: String?) async throws -> Data { guard !apiKey.isEmpty else { throw FindError.missingKey } var request = URLRequest(url: baseURL.appendingPathComponent("find/v1.1")) request.httpMethod = "POST" let boundary = "----nyris\(UUID().uuidString)" request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") request.setValue(apiKey, forHTTPHeaderField: "X-Api-Key") request.setValue("application/items.complete+json", forHTTPHeaderField: "Accept") var body = Data() func append(_ string: String) { body.append(Data(string.utf8)) } if let jpeg { append("--\(boundary)\r\n") append("Content-Disposition: form-data; name=\"image\"; filename=\"query.jpg\"\r\n") append("Content-Type: image/jpeg\r\n\r\n") body.append(jpeg) append("\r\n") } if let text, !text.isEmpty { append("--\(boundary)\r\n") append("Content-Disposition: form-data; name=\"text\"\r\n\r\n") append(text) append("\r\n") } append("--\(boundary)--\r\n") request.httpBody = body let (data, response) = try await URLSession.shared.data(for: request) let status = (response as? HTTPURLResponse)?.statusCode ?? 0 guard (200..<300).contains(status) else { throw FindError.http(status) } return data } func regions(jpeg: Data) async throws -> Data { guard !apiKey.isEmpty else { throw FindError.missingKey } var request = URLRequest(url: baseURL.appendingPathComponent("find/v2/regions")) request.httpMethod = "POST" request.setValue("image/jpeg", forHTTPHeaderField: "Content-Type") request.setValue(apiKey, forHTTPHeaderField: "X-Api-Key") request.httpBody = jpeg let (data, response) = try await URLSession.shared.data(for: request) let status = (response as? HTTPURLResponse)?.statusCode ?? 0 guard (200..<300).contains(status) else { throw FindError.http(status) } return data } }