44import librosa
55import base64
66import numpy as np
7- from typing import List , Tuple
7+ from typing import List , Tuple , Optional
88from io import BytesIO
99from concurrent .futures import ThreadPoolExecutor
1010from PIL import Image , ImageFile
1313from lightllm .utils .error_utils import ClientDisconnected
1414from lightllm .utils .multimodal_utils import fetch_resource
1515from lightllm .utils .log_utils import init_logger
16+ from lightllm .utils .envs_utils import get_env_start_args
1617
1718
1819logger = init_logger (__name__ )
@@ -131,6 +132,9 @@ def __init__(self, **kwargs):
131132 self .extra_params = {}
132133
133134 async def preload (self , request : Request ):
135+
136+ max_image_pixels = get_env_start_args ().max_image_pixels
137+
134138 try :
135139 if self ._type == "url" :
136140 timeout = int (os .getenv ("REQUEST_TIMEOUT" , "5" ))
@@ -141,8 +145,14 @@ async def preload(self, request: Request):
141145 elif self ._type == "image_size" :
142146 # image_size 代表直接传入图片的 width,height,主要是用于一些场景
143147 # 的 token 计数判断, 所以只需要图片长宽信息,不需要具体图片的内容信息
144- self .image_w = self ._data [0 ]
145- self .image_h = self ._data [1 ]
148+ src_w = self ._data [0 ]
149+ src_h = self ._data [1 ]
150+ self .image_w , self .image_h = _resize_image_dimensions_if_needed (src_w , src_h , max_image_pixels )
151+ if (self .image_w , self .image_h ) != (src_w , src_h ):
152+ logger .warning (
153+ f"image_size pixels { src_w * src_h } exceed max_image_pixels={ max_image_pixels } , "
154+ f"resized to { self .image_w } x{ self .image_h } "
155+ )
146156 return
147157 else :
148158 raise ValueError (f"cannot read image which type is { self ._type } !" )
@@ -151,7 +161,24 @@ async def preload(self, request: Request):
151161 # Decoding is mainly done in the C libraries (libjpeg/libpng/libwebp), which releases the GIL,
152162 # and multiple threads can achieve true parallelism.
153163 loop = asyncio .get_running_loop ()
154- self .image_w , self .image_h = await loop .run_in_executor (_IMAGE_VERIFY_POOL , _verify_image_bytes , img_data )
164+ # 1) Verify original input bytes first.
165+ src_w , src_h = await loop .run_in_executor (_IMAGE_VERIFY_POOL , _verify_image_bytes , img_data )
166+ # 2) Resize (or no-op) after verification.
167+ img_data , resized_w , resized_h = await loop .run_in_executor (
168+ _IMAGE_VERIFY_POOL ,
169+ _resize_image_bytes_if_needed ,
170+ img_data ,
171+ src_w ,
172+ src_h ,
173+ max_image_pixels ,
174+ )
175+ self .image_w , self .image_h = resized_w , resized_h
176+
177+ if (resized_w , resized_h ) != (src_w , src_h ):
178+ logger .warning (
179+ f"image pixels { src_w * src_h } exceed max_image_pixels={ max_image_pixels } ,"
180+ f" resized to { self .image_w } x{ self .image_h } "
181+ )
155182
156183 self ._preload_data = img_data
157184 return
@@ -245,3 +272,45 @@ def _verify_image_bytes(img_data: bytes) -> Tuple[int, int]:
245272 w , h = image .size
246273 image .load ()
247274 return w , h
275+
276+
277+ def _resize_image_bytes_if_needed (
278+ img_data : bytes , src_w : int , src_h : int , max_image_pixels : int
279+ ) -> Tuple [bytes , int , int ]:
280+ """
281+ Resize image bytes to satisfy max pixel constraint and return resized bytes with size.
282+ """
283+ new_w , new_h = _resize_image_dimensions_if_needed (src_w , src_h , max_image_pixels )
284+ if (new_w , new_h ) == (src_w , src_h ):
285+ return img_data , src_w , src_h
286+
287+ with Image .open (BytesIO (img_data )) as image :
288+ resampling = Image .Resampling .LANCZOS if hasattr (Image , "Resampling" ) else Image .LANCZOS
289+ resized_image = image .resize ((new_w , new_h ), resampling ).convert ("RGB" )
290+
291+ buffer = BytesIO ()
292+ resized_image .save (buffer , format = "JPEG" , quality = 96 , optimize = True )
293+ return buffer .getvalue (), new_w , new_h
294+
295+
296+ def _resize_image_dimensions_if_needed (src_w : int , src_h : int , max_image_pixels : int ) -> Tuple [int , int ]:
297+ """
298+ Compute resized (w, h) under a max pixel budget while preserving aspect ratio.
299+ """
300+ old_pixels = src_w * src_h
301+ if old_pixels <= max_image_pixels :
302+ return src_w , src_h
303+
304+ scale = (max_image_pixels / old_pixels ) ** 0.5
305+ new_w = max (1 , int (src_w * scale ))
306+ new_h = max (1 , int (src_h * scale ))
307+
308+ # Avoid overflow from integer rounding.
309+ while new_w * new_h > max_image_pixels :
310+ if new_w >= new_h :
311+ new_w = max (1 , new_w - 1 )
312+ else :
313+ new_h = max (1 , new_h - 1 )
314+
315+ assert new_w > 0 and new_h > 0 , "resized image dimensions must be positive"
316+ return new_w , new_h
0 commit comments