@@ -106,6 +106,25 @@ func (r *KittyRenderer) GetLastImageID() uint32 {
106106 return r .lastID
107107}
108108
109+ func reserveUnicodeImageNum (opts * KittyOptions ) (uint32 , error ) {
110+ const maxUnicodeImageNum = 0xFFFFFF
111+
112+ if opts != nil && opts .ImageNum > 0 {
113+ if opts .ImageNum > maxUnicodeImageNum {
114+ return 0 , fmt .Errorf ("unicode placeholders require image number <= 0xFFFFFF" )
115+ }
116+ return uint32 (opts .ImageNum ), nil
117+ }
118+
119+ imageNum := atomic .AddUint32 (& globalKittyImageNum , 1 )
120+ if imageNum > maxUnicodeImageNum {
121+ atomic .StoreUint32 (& globalKittyImageNum , 1 )
122+ imageNum = 1
123+ }
124+
125+ return imageNum , nil
126+ }
127+
109128// Render generates the escape sequence for displaying the image
110129func (r * KittyRenderer ) Render (img image.Image , opts RenderOptions ) (string , error ) {
111130 // Process the image (resize, dither, etc.)
@@ -121,7 +140,6 @@ func (r *KittyRenderer) Render(img image.Image, opts RenderOptions) (string, err
121140
122141 // Generate unique image ID using atomic counter to ensure uniqueness across all instances
123142 imageID := atomic .AddUint32 (& globalKittyImageID , 1 )
124- r .lastID = imageID
125143
126144 // Get image bounds
127145 bounds := processed .Bounds ()
@@ -147,22 +165,21 @@ func (r *KittyRenderer) Render(img image.Image, opts RenderOptions) (string, err
147165 rows = opts .Height
148166 }
149167
168+ kittyOpts := opts .KittyOpts
169+
150170 // Check if using Unicode placeholders - this requires a different two-step approach
151- useUnicode := opts . KittyOpts != nil && opts . KittyOpts .UseUnicode
171+ useUnicode := kittyOpts != nil && kittyOpts .UseUnicode
152172
153173 if useUnicode {
154174 // Two-step process for Unicode placeholders (matches old termimg behavior):
155175 // 1. Transmit image data (no display) using PNG format
156176 // 2. Create placement with U=1 and explicit cols/rows
157177 // 3. Generate placeholder characters
158178
159- // Use a small image ID that fits in 24 bits for RGB foreground color encoding
160- imageNum := atomic .AddUint32 (& globalKittyImageNum , 1 )
161- if imageNum > 0xFFFFFF {
162- atomic .StoreUint32 (& globalKittyImageNum , 1 )
163- imageNum = 1
179+ imageNum , err := reserveUnicodeImageNum (kittyOpts )
180+ if err != nil {
181+ return "" , err
164182 }
165- r .lastNum = imageNum
166183
167184 // Encode as PNG for transmission
168185 var buf bytes.Buffer
@@ -212,13 +229,16 @@ func (r *KittyRenderer) Render(img image.Image, opts RenderOptions) (string, err
212229 placeholders := r .generateUnicodePlaceholders (imageNum , cols , rows )
213230 output .WriteString (placeholders )
214231
232+ r .lastNum = imageNum
233+ r .lastID = imageNum
234+
215235 return output .String (), nil
216236 }
217237
218238 // Standard (non-Unicode) rendering path
219239 var data []byte
220240 var format string
221- if opts . KittyOpts != nil && opts . KittyOpts .PNG {
241+ if kittyOpts != nil && kittyOpts .PNG {
222242 format = "f=100"
223243 var b bytes.Buffer
224244 if err := png .Encode (& b , processed ); err != nil {
@@ -234,7 +254,7 @@ func (r *KittyRenderer) Render(img image.Image, opts RenderOptions) (string, err
234254
235255 // Check for compression
236256 var compressed bool
237- if opts . KittyOpts != nil && opts . KittyOpts .Compression {
257+ if kittyOpts != nil && kittyOpts .Compression {
238258 compressed = true
239259 var b bytes.Buffer
240260 w , err := zlib .NewWriterLevel (& b , zlib .BestSpeed )
@@ -268,13 +288,13 @@ func (r *KittyRenderer) Render(img image.Image, opts RenderOptions) (string, err
268288 // Build control data
269289 var control string
270290 var transferType string
271- if opts . KittyOpts != nil && opts . KittyOpts .TempFile {
291+ if kittyOpts != nil && kittyOpts .TempFile {
272292 transferType = ",t=t"
273293 }
274294
275295 imageIDStr := fmt .Sprintf ("i=%d" , imageID )
276- if opts . KittyOpts != nil && opts . KittyOpts .ImageNum > 0 {
277- imageIDStr = fmt .Sprintf ("I=%d" , opts . KittyOpts .ImageNum )
296+ if kittyOpts != nil && kittyOpts .ImageNum > 0 {
297+ imageIDStr = fmt .Sprintf ("I=%d" , kittyOpts .ImageNum )
278298 }
279299
280300 if opts .Virtual {
@@ -327,33 +347,36 @@ func (r *KittyRenderer) Render(img image.Image, opts RenderOptions) (string, err
327347 }
328348
329349 // Handle non-unicode Kitty options
330- if opts . KittyOpts != nil {
331- if opts .Virtual && ! opts . KittyOpts .UseUnicode {
350+ if kittyOpts != nil {
351+ if opts .Virtual && ! kittyOpts .UseUnicode {
332352 // Non-unicode virtual placement - just generate simple placeholders
333353 placeholders := r .generateUnicodePlaceholders (imageID , cols , rows )
334354 output .WriteString (placeholders )
335355 }
336356
337357 // Handle animation after image transfer
338- if opts . KittyOpts . Animation != nil && len (opts . KittyOpts .Animation .ImageIDs ) > 0 {
358+ if kittyOpts . Animation != nil && len (kittyOpts .Animation .ImageIDs ) > 0 {
339359 // TODO: Animation is handled separately after all images are transferred
340360 // This is just to validate the option structure
341361 }
342362
343363 // Handle positioning after image transfer
344- if opts . KittyOpts .Position != nil {
364+ if kittyOpts .Position != nil {
345365 // TODO: Positioning is handled separately via PlaceImage method
346366 // This is just to validate the option structure
347367 }
348368 }
349369
370+ r .lastID = imageID
350371 return output .String (), nil
351372}
352373
353374// Print outputs the image directly to stdout
354375func (r * KittyRenderer ) Print (img image.Image , opts RenderOptions ) error {
376+ kittyOpts := opts .KittyOpts
377+
355378 // Check if we should use file transfer optimization
356- if opts . KittyOpts != nil && opts . KittyOpts .FileTransfer {
379+ if kittyOpts != nil && kittyOpts .FileTransfer {
357380 // TODO: File transfer would require knowing the source file path
358381 // This is best handled at a higher level in the Image API
359382 // For now, fall back to regular rendering
@@ -366,18 +389,18 @@ func (r *KittyRenderer) Print(img image.Image, opts RenderOptions) error {
366389 _ , err = io .WriteString (os .Stdout , output )
367390
368391 // Handle post-render operations
369- if err == nil && opts . KittyOpts != nil {
392+ if err == nil && kittyOpts != nil {
370393 // Handle positioning if specified
371- if opts . KittyOpts .Position != nil {
394+ if kittyOpts .Position != nil {
372395 imageID := fmt .Sprintf ("%d" , r .lastID )
373- err = r .PlaceImage (imageID , opts . KittyOpts .Position .X ,
374- opts . KittyOpts . Position .Y , opts . KittyOpts .Position .ZIndex )
396+ err = r .PlaceImage (imageID , kittyOpts .Position .X ,
397+ kittyOpts . Position .Y , kittyOpts .Position .ZIndex )
375398 }
376399
377400 // Handle animation if specified
378- if opts . KittyOpts . Animation != nil && len (opts . KittyOpts .Animation .ImageIDs ) > 0 {
379- err = r .AnimateImages (opts . KittyOpts .Animation .ImageIDs ,
380- opts . KittyOpts . Animation .DelayMs , opts . KittyOpts .Animation .Loops )
401+ if kittyOpts . Animation != nil && len (kittyOpts .Animation .ImageIDs ) > 0 {
402+ err = r .AnimateImages (kittyOpts .Animation .ImageIDs ,
403+ kittyOpts . Animation .DelayMs , kittyOpts .Animation .Loops )
381404 }
382405 }
383406
0 commit comments