1- """Ultra-fast random image creation utilities for maximum throughput."""
1+ """Ultra-fast random image creation utilities for maximum throughput.
2+
3+ This file is intended to be used for generating benign test images for the
4+ purposes of integration testing the client, as is provided as a convenience
5+ for API consumers.
6+ """
27
38import asyncio
4- import io
59import random
610import time
711from collections .abc import AsyncIterator
812
9- from PIL import Image , ImageDraw
13+ import cv2 as cv
14+ import numpy as np
1015
1116from resolver_athena_client .client .models import ImageData
1217
1318# Global cache for reusable objects and constants
14- _image_cache : dict [
15- tuple [int , int ], tuple [Image .Image , ImageDraw .ImageDraw ]
16- ] = {}
19+ _image_cache : dict [tuple [int , int ], np .ndarray ] = {}
1720_rng = random .Random () # noqa: S311 - Not used for cryptographic purposes
1821
1922
20- def _get_cached_image (
21- width : int , height : int
22- ) -> tuple [Image .Image , ImageDraw .ImageDraw ]:
23- """Get cached image and draw objects, creating if needed."""
23+ def _get_cached_image (width : int , height : int ) -> np .ndarray :
24+ """Get cached image array, creating if needed."""
2425 key = (width , height )
2526 if key not in _image_cache :
26- img = Image .new ("RGB" , (width , height ), (0 , 0 , 0 ))
27- draw = ImageDraw .Draw (img )
28- _image_cache [key ] = (img , draw )
27+ img = np .zeros ((height , width , 3 ), dtype = np .uint8 )
28+ _image_cache [key ] = img
2929 return _image_cache [key ]
3030
3131
@@ -45,8 +45,9 @@ def create_random_image(
4545 PNG image bytes
4646
4747 """
48- # Get cached image and draw objects
49- image , draw = _get_cached_image (width , height )
48+ # Get cached image array
49+ image = _get_cached_image (width , height )
50+ img = image .copy ()
5051
5152 # Random background color
5253 bg_r , bg_g , bg_b = (
@@ -56,21 +57,24 @@ def create_random_image(
5657 )
5758
5859 # Fill with background color
59- draw . rectangle ([ 0 , 0 , width , height ], fill = ( bg_r , bg_g , bg_b ))
60+ img [:, :] = ( bg_b , bg_g , bg_r ) # OpenCV uses BGR
6061
6162 # Add single accent rectangle for visual variation
62- accent_color = (255 - bg_r , 255 - bg_g , 255 - bg_b )
63+ accent_color = (255 - bg_b , 255 - bg_g , 255 - bg_r ) # BGR
6364 x1 , y1 = width // 4 , height // 4
6465 x2 , y2 = (width * 3 ) // 4 , (height * 3 ) // 4
65- draw .rectangle ([ x1 , y1 , x2 , y2 ], fill = accent_color )
66+ img = cv .rectangle (img , ( x1 , y1 ), ( x2 , y2 ), accent_color , thickness = - 1 )
6667
6768 if img_format .upper () == "RAW_UINT8" :
68- return image .tobytes ()
69+ return img .tobytes ()
6970
70- # Convert to PNG bytes
71- buffer = io .BytesIO ()
72- image .save (buffer , format = img_format )
73- return buffer .getvalue ()
71+ # Convert to PNG/JPEG bytes
72+ ext = f".{ img_format .lower ()} "
73+ success , buf = cv .imencode (ext , img )
74+ if not success :
75+ err = f"Failed to encode image as { img_format } "
76+ raise RuntimeError (err )
77+ return buf .tobytes ()
7478
7579
7680def create_batch_images (
@@ -90,29 +94,32 @@ def create_batch_images(
9094
9195 """
9296 images : list [bytes ] = []
93- image , draw = _get_cached_image (width , height )
97+ image = _get_cached_image (width , height )
9498
9599 # Pre-calculate accent rectangle coordinates
96100 x1 , y1 = width // 4 , height // 4
97101 x2 , y2 = (width * 3 ) // 4 , (height * 3 ) // 4
98102
99103 for _ in range (count ):
104+ img = image .copy ()
100105 # Random background
101106 bg_r , bg_g , bg_b = (
102107 _rng .randint (0 , 255 ),
103108 _rng .randint (0 , 255 ),
104109 _rng .randint (0 , 255 ),
105110 )
106- draw . rectangle ([ 0 , 0 , width , height ], fill = ( bg_r , bg_g , bg_b ))
111+ img [:, :] = ( bg_b , bg_g , bg_r ) # OpenCV uses BGR
107112
108113 # Complement accent color
109- accent_color = (255 - bg_r , 255 - bg_g , 255 - bg_b )
110- draw .rectangle ([ x1 , y1 , x2 , y2 ], fill = accent_color )
114+ accent_color = (255 - bg_b , 255 - bg_g , 255 - bg_r ) # BGR
115+ img = cv .rectangle (img , ( x1 , y1 ), ( x2 , y2 ), accent_color , thickness = - 1 )
111116
112117 # Convert to PNG bytes
113- buffer = io .BytesIO ()
114- image .save (buffer , format = "PNG" )
115- images .append (buffer .getvalue ())
118+ success , buf = cv .imencode (".png" , img )
119+ if not success :
120+ msg = "Failed to encode image as PNG"
121+ raise RuntimeError (msg )
122+ images .append (buf .tobytes ())
116123
117124 return images
118125
@@ -196,18 +203,7 @@ async def rate_limited_image_iter(
196203def create_random_image_generator (
197204 max_images : int , rate_limit_min_interval_ms : int | None = None
198205) -> AsyncIterator [ImageData ]:
199- """Generate a stream of random test images.
200-
201- Args:
202- ----
203- max_images: Maximum number of images to generate
204- rate_limit_min_interval_ms: Minimum interval in ms between images
205-
206- Yields:
207- ------
208- ImageData objects containing random image bytes
209-
210- """
206+ """Create an async generator for images with optional rate limiting."""
211207 if rate_limit_min_interval_ms is not None :
212208 return rate_limited_image_iter (rate_limit_min_interval_ms , max_images )
213209
0 commit comments