Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 192 additions & 0 deletions OpenAI/src/Custom/Images/ImageClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System;
using System.ClientModel;
using System.ClientModel.Primitives;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
Expand Down Expand Up @@ -678,6 +679,134 @@ public virtual ClientResult<GeneratedImageCollection> GenerateImageEdits(string
return GenerateImageEdits(imageStream, imageFilePath, prompt, maskStream, maskFilePath, imageCount, options);
}

/// <summary> Generates edited or extended images based on multiple reference images and a prompt. </summary>
/// <param name="images">
/// The image streams to use as references. Each image must be a supported image file. The GPT image models accept
/// up to 16 reference images per request.
/// </param>
/// <param name="imageFilenames">
/// The filenames associated with each image stream, in the same order as <paramref name="images"/>. Each filename's
/// extension (for example: .png) will be used to validate the format of the corresponding input image. The request
/// may fail if a filename's extension and the actual format of its input image do not match.
/// </param>
/// <param name="prompt"> A text description of the desired image. </param>
/// <param name="options"> The options to configure the image edit. </param>
/// <param name="cancellationToken"> A token that can be used to cancel this method call. </param>
/// <exception cref="ArgumentNullException"> <paramref name="images"/>, <paramref name="imageFilenames"/>, or <paramref name="prompt"/> is null. </exception>
/// <exception cref="ArgumentException"> <paramref name="images"/> or <paramref name="imageFilenames"/> is empty, the two collections do not have the same number of elements, or <paramref name="prompt"/> is an empty string. </exception>
[Experimental("OPENAI001")]
public virtual async Task<ClientResult<GeneratedImageCollection>> GenerateImageEditsAsync(IEnumerable<Stream> images, IEnumerable<string> imageFilenames, string prompt, ImageEditOptions options = null, CancellationToken cancellationToken = default)
{
Argument.AssertNotNull(images, nameof(images));
Argument.AssertNotNull(imageFilenames, nameof(imageFilenames));
Argument.AssertNotNullOrEmpty(prompt, nameof(prompt));

List<Stream> imageList = images.ToList();
List<string> imageFilenameList = imageFilenames.ToList();
ValidateImages(imageList, imageFilenameList);

options ??= new();
options.Prompt = prompt;
options.Model = _model;

using MultiPartFormDataBinaryContent content = options.ToMultipartContent(imageList, imageFilenameList, null, null);
ClientResult result = await GenerateImageEditsAsync(content, content.ContentType, cancellationToken.ToRequestOptions()).ConfigureAwait(false);
return ClientResult.FromValue((GeneratedImageCollection)result, result.GetRawResponse());
}

/// <summary> Generates edited or extended images based on multiple reference images and a prompt. </summary>
/// <param name="images">
/// The image streams to use as references. Each image must be a supported image file. The GPT image models accept
/// up to 16 reference images per request.
/// </param>
/// <param name="imageFilenames">
/// The filenames associated with each image stream, in the same order as <paramref name="images"/>. Each filename's
/// extension (for example: .png) will be used to validate the format of the corresponding input image. The request
/// may fail if a filename's extension and the actual format of its input image do not match.
/// </param>
/// <param name="prompt"> A text description of the desired image. </param>
/// <param name="options"> The options to configure the image edit. </param>
/// <param name="cancellationToken"> A token that can be used to cancel this method call. </param>
/// <exception cref="ArgumentNullException"> <paramref name="images"/>, <paramref name="imageFilenames"/>, or <paramref name="prompt"/> is null. </exception>
/// <exception cref="ArgumentException"> <paramref name="images"/> or <paramref name="imageFilenames"/> is empty, the two collections do not have the same number of elements, or <paramref name="prompt"/> is an empty string. </exception>
[Experimental("OPENAI001")]
public virtual ClientResult<GeneratedImageCollection> GenerateImageEdits(IEnumerable<Stream> images, IEnumerable<string> imageFilenames, string prompt, ImageEditOptions options = null, CancellationToken cancellationToken = default)
{
Argument.AssertNotNull(images, nameof(images));
Argument.AssertNotNull(imageFilenames, nameof(imageFilenames));
Argument.AssertNotNullOrEmpty(prompt, nameof(prompt));

List<Stream> imageList = images.ToList();
List<string> imageFilenameList = imageFilenames.ToList();
ValidateImages(imageList, imageFilenameList);

options ??= new();
options.Prompt = prompt;
options.Model = _model;

using MultiPartFormDataBinaryContent content = options.ToMultipartContent(imageList, imageFilenameList, null, null);
ClientResult result = GenerateImageEdits(content, content.ContentType, cancellationToken.ToRequestOptions());
return ClientResult.FromValue((GeneratedImageCollection)result, result.GetRawResponse());
}

/// <summary> Generates edited or extended images based on multiple reference images and a prompt. </summary>
/// <param name="imageFilePaths">
/// The paths of the image files to use as references. Each image must be a supported image file. The GPT image
/// models accept up to 16 reference images per request. Each file path's extension (for example: .png) will be
/// used to validate the format of the corresponding input image. The request may fail if a file path's extension
/// and the actual format of its input image do not match.
/// </param>
/// <param name="prompt"> A text description of the desired image. </param>
/// <param name="options"> The options to configure the image edit. </param>
/// <param name="cancellationToken"> A token that can be used to cancel this method call. </param>
/// <exception cref="ArgumentNullException"> <paramref name="imageFilePaths"/> or <paramref name="prompt"/> is null. </exception>
/// <exception cref="ArgumentException"> <paramref name="imageFilePaths"/> is empty, or <paramref name="prompt"/> is an empty string. </exception>
[Experimental("OPENAI001")]
public virtual async Task<ClientResult<GeneratedImageCollection>> GenerateImageEditsAsync(IEnumerable<string> imageFilePaths, string prompt, ImageEditOptions options = null, CancellationToken cancellationToken = default)
{
Argument.AssertNotNull(imageFilePaths, nameof(imageFilePaths));

List<string> imageFilePathList = imageFilePaths.ToList();
List<FileStream> imageStreams = OpenImageFiles(imageFilePathList);
try
{
return await GenerateImageEditsAsync(imageStreams, imageFilePathList, prompt, options, cancellationToken).ConfigureAwait(false);
}
finally
{
DisposeImageFiles(imageStreams);
}
}

/// <summary> Generates edited or extended images based on multiple reference images and a prompt. </summary>
/// <param name="imageFilePaths">
/// The paths of the image files to use as references. Each image must be a supported image file. The GPT image
/// models accept up to 16 reference images per request. Each file path's extension (for example: .png) will be
/// used to validate the format of the corresponding input image. The request may fail if a file path's extension
/// and the actual format of its input image do not match.
/// </param>
/// <param name="prompt"> A text description of the desired image. </param>
/// <param name="options"> The options to configure the image edit. </param>
/// <param name="cancellationToken"> A token that can be used to cancel this method call. </param>
/// <exception cref="ArgumentNullException"> <paramref name="imageFilePaths"/> or <paramref name="prompt"/> is null. </exception>
/// <exception cref="ArgumentException"> <paramref name="imageFilePaths"/> is empty, or <paramref name="prompt"/> is an empty string. </exception>
[Experimental("OPENAI001")]
public virtual ClientResult<GeneratedImageCollection> GenerateImageEdits(IEnumerable<string> imageFilePaths, string prompt, ImageEditOptions options = null, CancellationToken cancellationToken = default)
{
Argument.AssertNotNull(imageFilePaths, nameof(imageFilePaths));

List<string> imageFilePathList = imageFilePaths.ToList();
List<FileStream> imageStreams = OpenImageFiles(imageFilePathList);
try
{
return GenerateImageEdits(imageStreams, imageFilePathList, prompt, options, cancellationToken);
}
finally
{
DisposeImageFiles(imageStreams);
}
}

#endregion

#region GenerateImageVariations
Expand Down Expand Up @@ -870,6 +999,69 @@ private void CreateImageEditOptions(Stream image, string imageFilename, string p
options.Model = _model;
}

private static void ValidateImages(List<Stream> images, List<string> imageFilenames)
{
if (images.Count == 0)
{
throw new ArgumentException("At least one image must be provided.", nameof(images));
}

if (images.Count != imageFilenames.Count)
{
throw new ArgumentException("The number of images and the number of image filenames must match.", nameof(imageFilenames));
}

for (int i = 0; i < images.Count; i++)
{
if (images[i] is null)
{
throw new ArgumentException("The collection of images cannot contain a null entry.", nameof(images));
}

if (string.IsNullOrEmpty(imageFilenames[i]))
{
throw new ArgumentException("The collection of image filenames cannot contain a null or empty entry.", nameof(imageFilenames));
}
}
}

private static List<FileStream> OpenImageFiles(List<string> imageFilePaths)
{
if (imageFilePaths.Count == 0)
{
throw new ArgumentException("At least one image file path must be provided.", nameof(imageFilePaths));
}

List<FileStream> imageStreams = new(imageFilePaths.Count);
try
{
foreach (string imageFilePath in imageFilePaths)
{
if (string.IsNullOrEmpty(imageFilePath))
{
throw new ArgumentException("The collection of image file paths cannot contain a null or empty entry.", nameof(imageFilePaths));
}

imageStreams.Add(File.OpenRead(imageFilePath));
}
}
catch
{
DisposeImageFiles(imageStreams);
throw;
}

return imageStreams;
}

private static void DisposeImageFiles(List<FileStream> imageStreams)
{
foreach (FileStream imageStream in imageStreams)
{
imageStream.Dispose();
}
}

private void CreateImageVariationOptions(Stream image, string imageFilename, int? imageCount, ref ImageVariationOptions options)
{
options.N = imageCount;
Expand Down
26 changes: 24 additions & 2 deletions OpenAI/src/Custom/Images/ImageEditOptions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Microsoft.TypeSpec.Generator.Customizations;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;

Expand Down Expand Up @@ -89,6 +90,29 @@ internal MultiPartFormDataBinaryContent ToMultipartContent(Stream image, string

content.Add(image, "image", imageFilename);

AddOptionsToContent(content, mask, maskFilename);

return content;
}

internal MultiPartFormDataBinaryContent ToMultipartContent(IReadOnlyList<Stream> images, IReadOnlyList<string> imageFilenames, Stream mask, string maskFilename)
{
MultiPartFormDataBinaryContent content = new();

// The image edit endpoint expects each image to be sent under the "image[]" field
// name when more than one image is provided, as shown in the platform documentation.
for (int i = 0; i < images.Count; i++)
{
content.Add(images[i], "image[]", imageFilenames[i]);
}

AddOptionsToContent(content, mask, maskFilename);

return content;
}

private void AddOptionsToContent(MultiPartFormDataBinaryContent content, Stream mask, string maskFilename)
{
content.Add(Prompt, "prompt");

if (Background is not null)
Expand Down Expand Up @@ -155,7 +179,5 @@ internal MultiPartFormDataBinaryContent ToMultipartContent(Stream image, string
{
content.Add(Stream.Value, "stream");
}

return content;
}
}
8 changes: 8 additions & 0 deletions api/released/net10.0/OpenAI.Images.net10.0.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,11 +179,19 @@ public class ImageClient {
public virtual Task<ClientResult<GeneratedImage>> GenerateImageEditAsync(string imageFilePath, string prompt, ImageEditOptions options = null);
public virtual Task<ClientResult<GeneratedImage>> GenerateImageEditAsync(string imageFilePath, string prompt, string maskFilePath, ImageEditOptions options = null);
public virtual ClientResult GenerateImageEdits(BinaryContent content, string contentType, RequestOptions options = null);
[Experimental("OPENAI001")]
public virtual ClientResult<GeneratedImageCollection> GenerateImageEdits(IEnumerable<Stream> images, IEnumerable<string> imageFilenames, string prompt, ImageEditOptions options = null, CancellationToken cancellationToken = default);
[Experimental("OPENAI001")]
public virtual ClientResult<GeneratedImageCollection> GenerateImageEdits(IEnumerable<string> imageFilePaths, string prompt, ImageEditOptions options = null, CancellationToken cancellationToken = default);
public virtual ClientResult<GeneratedImageCollection> GenerateImageEdits(Stream image, string imageFilename, string prompt, int imageCount, ImageEditOptions options = null, CancellationToken cancellationToken = default);
public virtual ClientResult<GeneratedImageCollection> GenerateImageEdits(Stream image, string imageFilename, string prompt, Stream mask, string maskFilename, int imageCount, ImageEditOptions options = null, CancellationToken cancellationToken = default);
public virtual ClientResult<GeneratedImageCollection> GenerateImageEdits(string imageFilePath, string prompt, int imageCount, ImageEditOptions options = null);
public virtual ClientResult<GeneratedImageCollection> GenerateImageEdits(string imageFilePath, string prompt, string maskFilePath, int imageCount, ImageEditOptions options = null);
public virtual Task<ClientResult> GenerateImageEditsAsync(BinaryContent content, string contentType, RequestOptions options = null);
[Experimental("OPENAI001")]
public virtual Task<ClientResult<GeneratedImageCollection>> GenerateImageEditsAsync(IEnumerable<Stream> images, IEnumerable<string> imageFilenames, string prompt, ImageEditOptions options = null, CancellationToken cancellationToken = default);
[Experimental("OPENAI001")]
public virtual Task<ClientResult<GeneratedImageCollection>> GenerateImageEditsAsync(IEnumerable<string> imageFilePaths, string prompt, ImageEditOptions options = null, CancellationToken cancellationToken = default);
public virtual Task<ClientResult<GeneratedImageCollection>> GenerateImageEditsAsync(Stream image, string imageFilename, string prompt, int imageCount, ImageEditOptions options = null, CancellationToken cancellationToken = default);
public virtual Task<ClientResult<GeneratedImageCollection>> GenerateImageEditsAsync(Stream image, string imageFilename, string prompt, Stream mask, string maskFilename, int imageCount, ImageEditOptions options = null, CancellationToken cancellationToken = default);
public virtual Task<ClientResult<GeneratedImageCollection>> GenerateImageEditsAsync(string imageFilePath, string prompt, int imageCount, ImageEditOptions options = null);
Expand Down
8 changes: 8 additions & 0 deletions api/released/net8.0/OpenAI.Images.net8.0.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,11 +179,19 @@ public class ImageClient {
public virtual Task<ClientResult<GeneratedImage>> GenerateImageEditAsync(string imageFilePath, string prompt, ImageEditOptions options = null);
public virtual Task<ClientResult<GeneratedImage>> GenerateImageEditAsync(string imageFilePath, string prompt, string maskFilePath, ImageEditOptions options = null);
public virtual ClientResult GenerateImageEdits(BinaryContent content, string contentType, RequestOptions options = null);
[Experimental("OPENAI001")]
public virtual ClientResult<GeneratedImageCollection> GenerateImageEdits(IEnumerable<Stream> images, IEnumerable<string> imageFilenames, string prompt, ImageEditOptions options = null, CancellationToken cancellationToken = default);
[Experimental("OPENAI001")]
public virtual ClientResult<GeneratedImageCollection> GenerateImageEdits(IEnumerable<string> imageFilePaths, string prompt, ImageEditOptions options = null, CancellationToken cancellationToken = default);
public virtual ClientResult<GeneratedImageCollection> GenerateImageEdits(Stream image, string imageFilename, string prompt, int imageCount, ImageEditOptions options = null, CancellationToken cancellationToken = default);
public virtual ClientResult<GeneratedImageCollection> GenerateImageEdits(Stream image, string imageFilename, string prompt, Stream mask, string maskFilename, int imageCount, ImageEditOptions options = null, CancellationToken cancellationToken = default);
public virtual ClientResult<GeneratedImageCollection> GenerateImageEdits(string imageFilePath, string prompt, int imageCount, ImageEditOptions options = null);
public virtual ClientResult<GeneratedImageCollection> GenerateImageEdits(string imageFilePath, string prompt, string maskFilePath, int imageCount, ImageEditOptions options = null);
public virtual Task<ClientResult> GenerateImageEditsAsync(BinaryContent content, string contentType, RequestOptions options = null);
[Experimental("OPENAI001")]
public virtual Task<ClientResult<GeneratedImageCollection>> GenerateImageEditsAsync(IEnumerable<Stream> images, IEnumerable<string> imageFilenames, string prompt, ImageEditOptions options = null, CancellationToken cancellationToken = default);
[Experimental("OPENAI001")]
public virtual Task<ClientResult<GeneratedImageCollection>> GenerateImageEditsAsync(IEnumerable<string> imageFilePaths, string prompt, ImageEditOptions options = null, CancellationToken cancellationToken = default);
public virtual Task<ClientResult<GeneratedImageCollection>> GenerateImageEditsAsync(Stream image, string imageFilename, string prompt, int imageCount, ImageEditOptions options = null, CancellationToken cancellationToken = default);
public virtual Task<ClientResult<GeneratedImageCollection>> GenerateImageEditsAsync(Stream image, string imageFilename, string prompt, Stream mask, string maskFilename, int imageCount, ImageEditOptions options = null, CancellationToken cancellationToken = default);
public virtual Task<ClientResult<GeneratedImageCollection>> GenerateImageEditsAsync(string imageFilePath, string prompt, int imageCount, ImageEditOptions options = null);
Expand Down
Loading