Skip to content

Commit 8722ad4

Browse files
google-genai-botcopybara-github
authored andcommitted
fix: Serialize Pillow images losslessly by default
PiperOrigin-RevId: 852690874
1 parent 33139db commit 8722ad4

2 files changed

Lines changed: 31 additions & 24 deletions

File tree

google/genai/_transformers.py

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -285,29 +285,23 @@ def t_caches_model(
285285
return model
286286

287287

288-
def pil_to_blob(img: Any) -> types.Blob:
289-
PngImagePlugin: Optional[builtin_types.ModuleType]
290-
try:
291-
import PIL.PngImagePlugin
292-
293-
PngImagePlugin = PIL.PngImagePlugin
294-
except ImportError:
295-
PngImagePlugin = None
296-
297-
bytesio = io.BytesIO()
288+
def pil_to_blob(image: Any) -> types.Blob:
289+
image_format = 'PNG'
290+
save_params: dict[str, Any] = dict()
298291
if (
299-
PngImagePlugin is not None
300-
and isinstance(img, PngImagePlugin.PngImageFile)
301-
or img.mode == 'RGBA'
292+
image.format == 'JPEG'
293+
and getattr(image, 'filename', '')
294+
and image.mode in ['1', 'L', 'RGB', 'RGBX', 'CMYK']
302295
):
303-
img.save(bytesio, format='PNG')
304-
mime_type = 'image/png'
305-
else:
306-
img.save(bytesio, format='JPEG')
307-
mime_type = 'image/jpeg'
308-
bytesio.seek(0)
309-
data = bytesio.read()
310-
return types.Blob(mime_type=mime_type, data=data)
296+
image_format = 'JPEG'
297+
save_params.update(quality='keep')
298+
299+
image_io = io.BytesIO()
300+
image.save(image_io, image_format, **save_params)
301+
image_bytes = image_io.getvalue()
302+
mime_type = f'image/{image_format.lower()}'
303+
304+
return types.Blob(data=image_bytes, mime_type=mime_type)
311305

312306

313307
def t_function_response(

google/genai/tests/transformers/test_blobs.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,17 @@
2626

2727

2828
def test_blob_dict():
29-
blob = t.t_blob({'data': bytes([0, 0, 0, 0, 0, 0]), 'mime_type': 'audio/pcm'}
30-
)
29+
blob = t.t_blob({
30+
'data': bytes([0, 0, 0, 0, 0, 0]),
31+
'mime_type': 'audio/pcm',
32+
})
3133
assert blob.data == bytes([0, 0, 0, 0, 0, 0])
3234
assert blob.mime_type == 'audio/pcm'
3335

3436

3537
def test_blob():
36-
blob = t.t_blob(types.Blob(data=bytes([0, 0, 0, 0, 0, 0]), mime_type='audio/pcm')
38+
blob = t.t_blob(
39+
types.Blob(data=bytes([0, 0, 0, 0, 0, 0]), mime_type='audio/pcm')
3740
)
3841
assert blob.data == bytes([0, 0, 0, 0, 0, 0])
3942
assert blob.mime_type == 'audio/pcm'
@@ -50,10 +53,12 @@ def test_image(image_jpeg):
5053
assert round_trip_image.mode == image_jpeg.mode
5154
assert round_trip_image.format == image_jpeg.format
5255

56+
5357
def test_not_image():
5458
blob = types.Blob(data=bytes([0, 0, 0, 0, 0, 0]), mime_type='audio/pcm')
5559
assert blob.as_image() is None
5660

61+
5762
def test_part_image(image_jpeg):
5863
part = t.t_part(image_jpeg)
5964
assert part.inline_data.data[6:10] == b'JFIF'
@@ -69,3 +74,11 @@ def test_part_image(image_jpeg):
6974
def test_part_not_image():
7075
part = t.t_part('hello world')
7176
assert part.as_image() is None
77+
78+
79+
def test_pil_to_blob_with_memory_pil_image():
80+
img = PIL.Image.new('RGB', (1, 1), color='red')
81+
blob = t.pil_to_blob(img)
82+
assert blob.mime_type == 'image/png'
83+
assert blob.data and len(blob.data) == 69
84+
assert blob.data[0:4] == b'\x89PNG'

0 commit comments

Comments
 (0)