Skip to content

Commit 51f9c17

Browse files
committed
Close #29; Fix compatibility with macOS with compile flags for UIImage and NSImage properties.
1 parent fe434cb commit 51f9c17

File tree

10 files changed

+166
-7
lines changed

10 files changed

+166
-7
lines changed

.github/FUNDING.yml~

Whitespace-only changes.

.github/ISSUE_TEMPLATE/bug_report.md~

Whitespace-only changes.

.github/ISSUE_TEMPLATE/feature_request.md~

Whitespace-only changes.

.github/workflows/swift.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,6 @@ jobs:
1717
steps:
1818
- uses: actions/checkout@v3
1919
- name: Build
20-
run: swift build -v -Xswiftc "-sdk" -Xswiftc "`xcrun --sdk iphonesimulator --show-sdk-path`" -Xswiftc "-target" -Xswiftc "x86_64-apple-ios13.0-simulator"
20+
run: swift build -v
2121
- name: Run tests
22-
run: xcodebuild test -scheme OpenAIKit -sdk iphonesimulator -destination "OS=16.0,name=iPhone 14 Pro Max"
22+
run: swift test -v

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,7 @@ OpenAIKit is a community-maintained API for the OpenAI REST endpoint used to get
4141

4242
| Platform | Minimum Swift Version | Installation | Status |
4343
| --- | --- | --- | --- |
44-
| iOS 13.0+ / tvOS 13.0+ / watchOS 6.0+ | 5.5 | [Swift Package Manager](#swift-package-manager) | Fully Tested |
45-
| macOS 10.15+ | N/A | N/A | Work in Progress |
44+
| iOS 13.0+ / macOS 10.15+ / tvOS 13.0+ / watchOS 6.0+ | 5.5 | [Swift Package Manager](#swift-package-manager) | Fully Tested |
4645

4746
## Installation
4847

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
//
2+
// NSImageExtension.swift
3+
// OpenAIKit
4+
//
5+
// Copyright (c) 2022 MarcoDotIO
6+
//
7+
// Permission is hereby granted, free of charge, to any person obtaining a copy
8+
// of this software and associated documentation files (the "Software"), to deal
9+
// in the Software without restriction, including without limitation the rights
10+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
// copies of the Software, and to permit persons to whom the Software is
12+
// furnished to do so, subject to the following conditions:
13+
//
14+
// The above copyright notice and this permission notice shall be included in
15+
// all copies or substantial portions of the Software.
16+
//
17+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
// THE SOFTWARE.
24+
//
25+
26+
#if os(macOS)
27+
import Cocoa
28+
29+
extension NSImage {
30+
public func pngData(
31+
size: ImageResolutions,
32+
imageInterpolation: NSImageInterpolation = .high
33+
) -> Data? {
34+
var cgSize = CGSize()
35+
36+
switch size {
37+
case .small:
38+
cgSize.width = 256
39+
cgSize.height = 256
40+
break
41+
case .medium:
42+
cgSize.width = 512
43+
cgSize.height = 512
44+
break
45+
case .large:
46+
cgSize.width = 1024
47+
cgSize.height = 1024
48+
break
49+
}
50+
51+
guard let bitmap = NSBitmapImageRep(
52+
bitmapDataPlanes: nil,
53+
pixelsWide: Int(cgSize.width),
54+
pixelsHigh: Int(cgSize.height),
55+
bitsPerSample: 8,
56+
samplesPerPixel: 4,
57+
hasAlpha: true,
58+
isPlanar: false,
59+
colorSpaceName: .deviceRGB,
60+
bitmapFormat: [],
61+
bytesPerRow: 0,
62+
bitsPerPixel: 0
63+
) else {
64+
return nil
65+
}
66+
67+
bitmap.size = cgSize
68+
NSGraphicsContext.saveGraphicsState()
69+
NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: bitmap)
70+
NSGraphicsContext.current?.imageInterpolation = imageInterpolation
71+
draw(
72+
in: NSRect(origin: .zero, size: cgSize),
73+
from: .zero,
74+
operation: .copy,
75+
fraction: 1.0
76+
)
77+
NSGraphicsContext.restoreGraphicsState()
78+
79+
return bitmap.representation(using: .png, properties: [:])
80+
}
81+
}
82+
#endif

Sources/OpenAIKit/OpenAI.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@
2525

2626
import SwiftUI
2727

28+
#if os(iOS) || os(tvOS)
29+
import UIKit
30+
#endif
31+
2832
/// OpenAI provides the needed core functions of OpenAIKit.
2933
@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, *)
3034
public final class OpenAI {
@@ -35,6 +39,7 @@ public final class OpenAI {
3539
self.config = config
3640
}
3741

42+
#if os(iOS) || os(tvOS)
3843
/// Input a `Base64` image binary `String` to receive an `UIImage` object.
3944
/// - Parameter b64Data: The `Base64` data itself in `String` form.
4045
/// - Returns: A `UIImage` object.
@@ -53,6 +58,28 @@ public final class OpenAI {
5358
throw OpenAIError.invalidData
5459
}
5560
}
61+
#endif
62+
63+
#if os(macOS)
64+
/// Input a `Base64` image binary `String` to receive an `NSImage` object.
65+
/// - Parameter b64Data: The `Base64` data itself in `String` form.
66+
/// - Returns: A `UIImage` object.
67+
public func decodeBase64Image(_ b64Data: String) throws -> NSImage {
68+
do {
69+
guard let data = Data(base64Encoded: b64Data) else {
70+
throw OpenAIError.invalidData
71+
}
72+
73+
guard let image = NSImage(data: data) else {
74+
throw OpenAIError.invalidData
75+
}
76+
77+
return image
78+
} catch {
79+
throw OpenAIError.invalidData
80+
}
81+
}
82+
#endif
5683

5784
/// Return a `URL` with the OpenAI API endpoint as the `URL`
5885
/// - Parameter path: The `String` path.

Sources/OpenAIKit/Protocols/OpenAIProtocol.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@
2323
// THE SOFTWARE.
2424
//
2525

26-
import UIKit
27-
2826
public protocol OpenAIProtocol {
2927
// MARK: Models Functions
3028
/// List and describe the various models available in the API. You can refer to the [Models](https://beta.openai.com/docs/models)

Sources/OpenAIKit/Types/Structs/Parameters/Images/ImageEditParameters.swift

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ public struct ImageEditParameters {
6161
/// A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse.
6262
/// [Learn more.](https://beta.openai.com/docs/guides/safety-best-practices/end-user-ids)
6363
public var user: String?
64-
64+
65+
#if os(iOS) || os(tvOS) || os(watchOS)
6566
public init(
6667
image: UIImage,
6768
mask: UIImage,
@@ -86,6 +87,34 @@ public struct ImageEditParameters {
8687
throw OpenAIError.invalidData
8788
}
8889
}
90+
#endif
91+
92+
#if os(macOS)
93+
public init(
94+
image: NSImage,
95+
mask: NSImage,
96+
prompt: String,
97+
@Clamped(range: 1...10) numberOfImages: Int = 1,
98+
resolution: ImageResolutions = .large,
99+
responseFormat: ResponseFormat = .url,
100+
user: String? = nil
101+
) throws {
102+
do {
103+
guard let imageData = image.pngData(size: resolution) else { throw OpenAIError.invalidData }
104+
guard let maskData = mask.pngData(size: resolution) else { throw OpenAIError.invalidData }
105+
106+
self.image = FormData(data: imageData, mimeType: "image/png", fileName: "image.png")
107+
self.mask = FormData(data: maskData, mimeType: "image/png", fileName: "mask.png")
108+
self.prompt = prompt
109+
self.numberOfImages = numberOfImages
110+
self.resolution = resolution
111+
self.responseFormat = responseFormat
112+
self.user = user
113+
} catch {
114+
throw OpenAIError.invalidData
115+
}
116+
}
117+
#endif
89118

90119
/// The body of the URL used for OpenAI API requests.
91120
public var body: [String: Any] {

Sources/OpenAIKit/Types/Structs/Parameters/Images/ImageVariationParameters.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ public struct ImageVariationParameters {
5151
/// [Learn more.](https://beta.openai.com/docs/guides/safety-best-practices/end-user-ids)
5252
public var user: String?
5353

54+
#if os(iOS) || os(tvOS) || os(watchOS)
5455
public init(
5556
image: UIImage,
5657
@Clamped(range: 1...10) numberOfImages: Int = 1,
@@ -70,7 +71,30 @@ public struct ImageVariationParameters {
7071
throw OpenAIError.invalidData
7172
}
7273
}
74+
#endif
75+
76+
#if os(macOS)
77+
public init(
78+
image: NSImage,
79+
@Clamped(range: 1...10) numberOfImages: Int = 1,
80+
resolution: ImageResolutions = .large,
81+
responseFormat: ResponseFormat = .url,
82+
user: String? = nil
83+
) throws {
84+
do {
85+
guard let imageData = image.pngData(size: resolution) else { throw OpenAIError.invalidData }
7386

87+
self.image = FormData(data: imageData, mimeType: "image/png", fileName: "image.png")
88+
self.numberOfImages = numberOfImages
89+
self.resolution = resolution
90+
self.responseFormat = responseFormat
91+
self.user = user
92+
} catch {
93+
throw OpenAIError.invalidData
94+
}
95+
}
96+
#endif
97+
7498
/// The body of the URL used for OpenAI API requests.
7599
public var body: [String: Any] {
76100
var result: [String: Any] = ["image": self.image,

0 commit comments

Comments
 (0)