diff --git a/OpenAI_API/EndpointBase.cs b/OpenAI_API/EndpointBase.cs index 792727a..2a24045 100644 --- a/OpenAI_API/EndpointBase.cs +++ b/OpenAI_API/EndpointBase.cs @@ -1,10 +1,13 @@ using Newtonsoft.Json; +using OpenAI_API.Moderation; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net.Http; +using System.Net.Http.Headers; +using System.Net.Mime; using System.Security.Authentication; using System.Text; using System.Threading.Tasks; @@ -119,21 +122,35 @@ private async Task HttpRequestRaw(string url = null, HttpMe if (postData != null) { - if (postData is HttpContent) - { - req.Content = postData as HttpContent; - } - else - { - string jsonContent = JsonConvert.SerializeObject(postData, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore }); - var stringContent = new StringContent(jsonContent, UnicodeEncoding.UTF8, "application/json"); - req.Content = stringContent; - } - } - response = await client.SendAsync(req, streaming ? HttpCompletionOption.ResponseHeadersRead : HttpCompletionOption.ResponseContentRead); - - if (response.IsSuccessStatusCode) - { + if (postData is HttpContent) + { + req.Content = postData as HttpContent; + response = await client.SendAsync(req, streaming ? HttpCompletionOption.ResponseHeadersRead : HttpCompletionOption.ResponseContentRead); + } + else if (postData is OpenAI_API.Images.ImageEditRequest) + { + var data = postData as Images.ImageEditRequest; + byte[] byes_array = File.ReadAllBytes(data.Image); + + MultipartFormDataContent formData = new MultipartFormDataContent + { + { new ByteArrayContent(byes_array, 0, byes_array.Length), "image", "image.png"}, + { new StringContent(data.Prompt), "prompt" }, + }; + + response = await client.PostAsync(url, formData); + } + else + { + string jsonContent = JsonConvert.SerializeObject(postData, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore }); + var stringContent = new StringContent(jsonContent, UnicodeEncoding.UTF8, "application/json"); + req.Content = stringContent; + response = await client.SendAsync(req, streaming ? HttpCompletionOption.ResponseHeadersRead : HttpCompletionOption.ResponseContentRead); + } + } + + if (response.IsSuccessStatusCode) + { return response; } else diff --git a/OpenAI_API/Images/IImageEditEndpoint.cs b/OpenAI_API/Images/IImageEditEndpoint.cs new file mode 100644 index 0000000..db93c08 --- /dev/null +++ b/OpenAI_API/Images/IImageEditEndpoint.cs @@ -0,0 +1,17 @@ +using System.Threading.Tasks; + +namespace OpenAI_API.Images +{ + /// + /// An interface for . Given a prompt, the model will generate a new image. + /// + public interface IImageEditEndpoint + { + /// + /// Ask the API to Creates an image given a prompt. + /// + /// Request to be send + /// Asynchronously returns the image result. Look in its + Task EditImageAsync(ImageEditRequest request); + } +} \ No newline at end of file diff --git a/OpenAI_API/Images/ImageEditEndpoint.cs b/OpenAI_API/Images/ImageEditEndpoint.cs new file mode 100644 index 0000000..bdf28de --- /dev/null +++ b/OpenAI_API/Images/ImageEditEndpoint.cs @@ -0,0 +1,36 @@ +using OpenAI_API.Models; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace OpenAI_API.Images +{ + /// + /// Given a prompt, the model will generate a new image. + /// + public class ImageEditEndpoint : EndpointBase, IImageEditEndpoint + { + /// + /// The name of the endpoint, which is the final path segment in the API URL. For example, "image". + /// + protected override string Endpoint { get { return "images/edits"; } } + + /// + /// Constructor of the api endpoint. Rather than instantiating this yourself, access it through an instance of as . + /// + /// + internal ImageEditEndpoint(OpenAIAPI api) : base(api) { } + + + /// + /// Ask the API to Creates an image given a prompt. + /// + /// Request to be send + /// Asynchronously returns the image result. Look in its + public async Task EditImageAsync(ImageEditRequest request) + { + return await HttpPost(postData: request); + } + } +} diff --git a/OpenAI_API/Images/ImageEditRequest.cs b/OpenAI_API/Images/ImageEditRequest.cs new file mode 100644 index 0000000..7fc2edc --- /dev/null +++ b/OpenAI_API/Images/ImageEditRequest.cs @@ -0,0 +1,90 @@ +using Newtonsoft.Json; +using OpenAI_API.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace OpenAI_API.Images +{ + /// + /// Represents a request to the Images API. Mostly matches the parameters in the OpenAI docs, although some have been renamed or expanded into single/multiple properties for ease of use. + /// + public class ImageEditRequest + { + /// + /// The image to edit. Must be a valid PNG file, less than 4MB, and square. If mask is not provided, image must have transparency, which will be used as the mask. + /// + [JsonProperty("image")] + public string Image { get; set; } + + /// + /// An additional image whose fully transparent areas (e.g. where alpha is zero) indicate where image should be edited. Must be a valid PNG file, less than 4MB, and have the same. + /// + [JsonProperty("mask")] + public string Mask { get; set; } + + /// + /// A text description of the desired image(s). The maximum length is 1000 characters. + /// + [JsonProperty("prompt")] + public string Prompt { get; set; } + + + [JsonProperty("n")] + public int? NumOfImages { get; set; } = 1; + + /// + /// The size of the generated images. Must be one of 256x256, 512x512, or 1024x1024. Defauls to 1024x1024 + /// + [JsonProperty("size"), JsonConverter(typeof(ImageSize.ImageSizeJsonConverter))] + public ImageSize Size { get; set; } + + /// + /// The format in which the generated images are returned. Must be one of url or b64_json. Defaults to Url. + /// + [JsonProperty("response_format"), JsonConverter(typeof(ImageResponseFormat.ImageResponseJsonConverter))] + public ImageResponseFormat ResponseFormat { get; set; } + + /// + /// A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. Optional. + /// + [JsonProperty("user")] + public string User { get; set; } + + + /// + /// Cretes a new, empty + /// + public ImageEditRequest() + { + + } + + /// + /// Creates a new with the specified parameters + /// + /// + /// A text description of the desired image(s). The maximum length is 1000 characters. + /// How many different choices to request for each prompt. Defaults to 1. + /// The size of the generated images. Must be one of 256x256, 512x512, or 1024x1024. + /// A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. + /// The format in which the generated images are returned. Must be one of url or b64_json. + public ImageEditRequest( + string image, + string prompt, + int? numOfImages = 1, + ImageSize size = null, + string user = null, + ImageResponseFormat responseFormat = null) + { + this.Image = image; + this.Prompt = prompt; + this.NumOfImages = numOfImages; + this.User = user; + this.Size = size ?? ImageSize._1024; + this.ResponseFormat = responseFormat ?? ImageResponseFormat.Url; + } + + } +} diff --git a/OpenAI_API/OpenAIAPI.cs b/OpenAI_API/OpenAIAPI.cs index f1e2bda..5d1db34 100644 --- a/OpenAI_API/OpenAIAPI.cs +++ b/OpenAI_API/OpenAIAPI.cs @@ -50,6 +50,7 @@ public OpenAIAPI(APIAuthentication apiKeys = null) Chat = new ChatEndpoint(this); Moderation = new ModerationEndpoint(this); ImageGenerations = new ImageGenerationEndpoint(this); + ImageEdit= new ImageEditEndpoint(this); } /// @@ -100,6 +101,11 @@ public static OpenAIAPI ForAzure(string YourResourceName, string deploymentId, A /// /// The API lets you do operations with images. Given a prompt and/or an input image, the model will generate a new image. /// - public IImageGenerationEndpoint ImageGenerations { get; } - } + public IImageGenerationEndpoint ImageGenerations { get; } + + /// + /// The API lets you do operations with images. Given a prompt and an input image, the model will edit a new image. + /// + public IImageEditEndpoint ImageEdit { get; } + } } diff --git a/OpenAI_Tests/ImageEditEndpointTests.cs b/OpenAI_Tests/ImageEditEndpointTests.cs new file mode 100644 index 0000000..67de5fb --- /dev/null +++ b/OpenAI_Tests/ImageEditEndpointTests.cs @@ -0,0 +1,76 @@ +using NUnit.Framework; +using OpenAI_API.Images; +using OpenAI_API.Moderation; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; +using System.Text; + +namespace OpenAI_Tests +{ + public class ImageEditEndpointTests + { + [SetUp] + public void Setup() + { + OpenAI_API.APIAuthentication.Default = new OpenAI_API.APIAuthentication(Environment.GetEnvironmentVariable("TEST_OPENAI_SECRET_KEY")); + } + + private string GetImage(string path) + { + byte[] imageArray = System.IO.File.ReadAllBytes(path); + string base64ImageRepresentation = Convert.ToBase64String(imageArray); + return base64ImageRepresentation; + } + + [Test] + public void EditImage() + { + string imageFilepath = Path.Combine(AppContext.BaseDirectory, "images\\EditImage.png"); + string prompt = "add flowers, digital art"; + + Assert.That(File.Exists(imageFilepath)); + EditImage(imageFilepath, prompt); + } + + private void EditImage(string imageFilepath, string prompt) + { + var api = new OpenAI_API.OpenAIAPI(); + + Assert.IsNotNull(api.ImageEdit); + Assert.IsTrue(File.Exists(imageFilepath)); + var imageEditRequest = new ImageEditRequest(imageFilepath, prompt, 1, ImageSize._256); + + ImageResult results = null; + + try + { + results = api.ImageEdit.EditImageAsync(imageEditRequest).Result; + } + catch (Exception ex) + { + } + + Assert.IsNotNull(results); + if (results.CreatedUnixTime.HasValue) + { + Assert.NotZero(results.CreatedUnixTime.Value); + Assert.NotNull(results.Created); + Assert.Greater(results.Created.Value, new DateTime(2018, 1, 1)); + Assert.Less(results.Created.Value, DateTime.Now.AddDays(1)); + } + else + { + Assert.Null(results.Created); + } + + Assert.NotZero(results.Data.Count); + Assert.NotNull(results.Data.First().Url); + Assert.That(results.Data.First().Url.Length > 0); + Assert.That(results.Data.First().Url.StartsWith("https://")); + } + } +} diff --git a/OpenAI_Tests/OpenAI_Tests.csproj b/OpenAI_Tests/OpenAI_Tests.csproj index f26766e..af295d2 100644 --- a/OpenAI_Tests/OpenAI_Tests.csproj +++ b/OpenAI_Tests/OpenAI_Tests.csproj @@ -22,6 +22,13 @@ PreserveNewest + + Always + + + + + diff --git a/OpenAI_Tests/images/EditImage.png b/OpenAI_Tests/images/EditImage.png new file mode 100644 index 0000000..eaac01e Binary files /dev/null and b/OpenAI_Tests/images/EditImage.png differ