@@ -7,7 +7,7 @@ namespace ImageGenCli;
77
88/// <summary>
99/// Image generation client for OpenAI API.
10- /// Supports gpt-image-1.5 and gpt-image-1 models.
10+ /// Supports gpt-image-2, gpt-image- 1.5, gpt-image-1, and gpt-image-1-mini models.
1111/// </summary>
1212public class OpenAIImageClient : IImageGenerationClient
1313{
@@ -20,8 +20,8 @@ public class OpenAIImageClient : IImageGenerationClient
2020 /// Creates a new OpenAI image client.
2121 /// </summary>
2222 /// <param name="apiKey">The OpenAI API key.</param>
23- /// <param name="model">The model to use (default: gpt-image-1.5 ).</param>
24- public OpenAIImageClient ( string apiKey , string model = "gpt-image-1.5 " )
23+ /// <param name="model">The model to use (default: gpt-image-2 ).</param>
24+ public OpenAIImageClient ( string apiKey , string model = "gpt-image-2 " )
2525 {
2626 _apiKey = apiKey ;
2727 _model = model ;
@@ -54,7 +54,7 @@ private async Task<string> GenerateTextOnlyAsync(GenerationRequest request, Canc
5454 [ "model" ] = _model ,
5555 [ "prompt" ] = request . Prompt ,
5656 [ "n" ] = request . NumberOfImages ,
57- [ "size" ] = MapSize ( request . AspectRatio )
57+ [ "size" ] = ResolveSize ( request )
5858 } ;
5959
6060 if ( ! string . IsNullOrEmpty ( request . Quality ) )
@@ -85,7 +85,7 @@ private async Task<string> GenerateWithImagesAsync(GenerationRequest request, Ca
8585 form . Add ( new StringContent ( _model ) , "model" ) ;
8686 form . Add ( new StringContent ( request . Prompt ) , "prompt" ) ;
8787 form . Add ( new StringContent ( request . NumberOfImages . ToString ( ) ) , "n" ) ;
88- form . Add ( new StringContent ( MapSize ( request . AspectRatio ) ) , "size" ) ;
88+ form . Add ( new StringContent ( ResolveSize ( request ) ) , "size" ) ;
8989
9090 if ( ! string . IsNullOrEmpty ( request . Quality ) )
9191 {
@@ -141,6 +141,18 @@ private static GenerationResult ParseResponse(string content)
141141 return result ;
142142 }
143143
144+ private string ResolveSize ( GenerationRequest request )
145+ {
146+ if ( _model == "gpt-image-2" && ! string . IsNullOrEmpty ( request . Resolution ) && request . Resolution != "1K" )
147+ {
148+ if ( TryResolveSize ( request . Resolution , out var size , out _ ) )
149+ {
150+ return size ;
151+ }
152+ }
153+ return MapSize ( request . AspectRatio ) ;
154+ }
155+
144156 private static string MapSize ( string aspectRatio )
145157 {
146158 // OpenAI uses pixel dimensions, map from aspect ratio
@@ -154,4 +166,71 @@ private static string MapSize(string aspectRatio)
154166 _ => "1024x1024"
155167 } ;
156168 }
169+
170+ /// <summary>
171+ /// Resolves a --resolution value for gpt-image-2, accepting either WxH (e.g. "2048x1152")
172+ /// or an aspect ratio (e.g. "3:2"). Validates gpt-image-2 constraints client-side:
173+ /// each edge a multiple of 16, max edge ≤ 3840, long:short ratio ≤ 3:1,
174+ /// total pixels in [655,360, 8,294,400].
175+ /// </summary>
176+ public static bool TryResolveSize ( string input , out string size , out string error )
177+ {
178+ size = "" ;
179+ error = "" ;
180+
181+ // Aspect-ratio form → pick a sensible preset.
182+ if ( input . Contains ( ':' ) && ! input . Contains ( 'x' , StringComparison . OrdinalIgnoreCase ) )
183+ {
184+ size = input switch
185+ {
186+ "1:1" => "2048x2048" ,
187+ "3:2" or "16:9" => "2048x1152" ,
188+ "2:3" or "9:16" => "1152x2048" ,
189+ "4:3" => "2048x1536" ,
190+ "3:4" => "1536x2048" ,
191+ _ => ""
192+ } ;
193+ if ( size == "" )
194+ {
195+ error = $ "Unsupported aspect ratio '{ input } ' for gpt-image-2 --resolution. Try WxH (e.g. 2048x1152) or 1:1, 3:2, 2:3, 16:9, 9:16, 4:3, 3:4.";
196+ return false ;
197+ }
198+ return true ;
199+ }
200+
201+ // WxH form.
202+ var parts = input . ToLowerInvariant ( ) . Split ( 'x' ) ;
203+ if ( parts . Length != 2 || ! int . TryParse ( parts [ 0 ] , out var w ) || ! int . TryParse ( parts [ 1 ] , out var h ) )
204+ {
205+ error = $ "Invalid --resolution '{ input } '. Expected WxH (e.g. 2048x1152) or aspect ratio (e.g. 3:2).";
206+ return false ;
207+ }
208+
209+ if ( w % 16 != 0 || h % 16 != 0 )
210+ {
211+ error = $ "gpt-image-2 requires both dimensions be multiples of 16 (got { w } x{ h } ).";
212+ return false ;
213+ }
214+ if ( w > 3840 || h > 3840 )
215+ {
216+ error = $ "gpt-image-2 max edge length is 3840px (got { w } x{ h } ).";
217+ return false ;
218+ }
219+ var longEdge = Math . Max ( w , h ) ;
220+ var shortEdge = Math . Min ( w , h ) ;
221+ if ( longEdge > shortEdge * 3 )
222+ {
223+ error = $ "gpt-image-2 long:short edge ratio must not exceed 3:1 (got { w } x{ h } ).";
224+ return false ;
225+ }
226+ long pixels = ( long ) w * h ;
227+ if ( pixels < 655_360 || pixels > 8_294_400 )
228+ {
229+ error = $ "gpt-image-2 total pixels must be between 655,360 and 8,294,400 (got { pixels : N0} from { w } x{ h } ).";
230+ return false ;
231+ }
232+
233+ size = $ "{ w } x{ h } ";
234+ return true ;
235+ }
157236}
0 commit comments