maybeResizeAndDownsampleImageBuffer method
Resizes image buffer to meet size and dimension constraints.
Implementation
Future<ResizeResult> maybeResizeAndDownsampleImageBuffer(
Uint8List imageBuffer,
int originalSize,
String ext,
) async {
if (imageBuffer.isEmpty) {
throw ImageResizeError('Image file is empty (0 bytes)');
}
try {
if (_imageProcessor == null || _getImageMetadata == null) {
throw StateError('Image processor not configured');
}
final metadata = await _getImageMetadata!(imageBuffer);
final mediaType = metadata.format ?? ext;
final normalizedMediaType = mediaType == 'jpg' ? 'jpeg' : mediaType;
if (metadata.width == null || metadata.height == null) {
if (originalSize > imageTargetRawSize) {
final compressed = await _imageProcessor!(
imageBuffer,
format: 'jpeg',
quality: 80,
);
return ResizeResult(buffer: compressed, mediaType: 'jpeg');
}
return ResizeResult(
buffer: imageBuffer,
mediaType: normalizedMediaType,
);
}
final originalWidth = metadata.width!;
final originalHeight = metadata.height!;
int width = originalWidth;
int height = originalHeight;
// Check if original just works
if (originalSize <= imageTargetRawSize &&
width <= imageMaxWidth &&
height <= imageMaxHeight) {
return ResizeResult(
buffer: imageBuffer,
mediaType: normalizedMediaType,
dimensions: ImageDimensions(
originalWidth: originalWidth,
originalHeight: originalHeight,
displayWidth: width,
displayHeight: height,
),
);
}
final needsDimensionResize =
width > imageMaxWidth || height > imageMaxHeight;
final isPng = normalizedMediaType == 'png';
// If dimensions are within limits but file is too large, try compression
if (!needsDimensionResize && originalSize > imageTargetRawSize) {
if (isPng) {
final pngCompressed = await _imageProcessor!(
imageBuffer,
format: 'png',
compressionLevel: 9,
palette: true,
);
if (pngCompressed.length <= imageTargetRawSize) {
return ResizeResult(
buffer: pngCompressed,
mediaType: 'png',
dimensions: ImageDimensions(
originalWidth: originalWidth,
originalHeight: originalHeight,
displayWidth: width,
displayHeight: height,
),
);
}
}
for (final quality in [80, 60, 40, 20]) {
final compressed = await _imageProcessor!(
imageBuffer,
format: 'jpeg',
quality: quality,
);
if (compressed.length <= imageTargetRawSize) {
return ResizeResult(
buffer: compressed,
mediaType: 'jpeg',
dimensions: ImageDimensions(
originalWidth: originalWidth,
originalHeight: originalHeight,
displayWidth: width,
displayHeight: height,
),
);
}
}
}
// Constrain dimensions
if (width > imageMaxWidth) {
height = (height * imageMaxWidth / width).round();
width = imageMaxWidth;
}
if (height > imageMaxHeight) {
width = (width * imageMaxHeight / height).round();
height = imageMaxHeight;
}
_logForDebugging('Resizing to ${width}x$height');
final resized = await _imageProcessor!(
imageBuffer,
width: width,
height: height,
);
// If still too large, try compression
if (resized.length > imageTargetRawSize) {
for (final quality in [80, 60, 40, 20]) {
final compressed = await _imageProcessor!(
imageBuffer,
width: width,
height: height,
format: 'jpeg',
quality: quality,
);
if (compressed.length <= imageTargetRawSize) {
return ResizeResult(
buffer: compressed,
mediaType: 'jpeg',
dimensions: ImageDimensions(
originalWidth: originalWidth,
originalHeight: originalHeight,
displayWidth: width,
displayHeight: height,
),
);
}
}
// Last resort: smaller dimensions + aggressive compression
final smallerWidth = min(width, 1000);
final smallerHeight = (height * smallerWidth / max(width, 1)).round();
final compressed = await _imageProcessor!(
imageBuffer,
width: smallerWidth,
height: smallerHeight,
format: 'jpeg',
quality: 20,
);
return ResizeResult(
buffer: compressed,
mediaType: 'jpeg',
dimensions: ImageDimensions(
originalWidth: originalWidth,
originalHeight: originalHeight,
displayWidth: smallerWidth,
displayHeight: smallerHeight,
),
);
}
return ResizeResult(
buffer: resized,
mediaType: normalizedMediaType,
dimensions: ImageDimensions(
originalWidth: originalWidth,
originalHeight: originalHeight,
displayWidth: width,
displayHeight: height,
),
);
} catch (error) {
_logError(error);
final errorType = classifyImageError(error);
final detected = detectImageFormatFromBuffer(imageBuffer);
final normalizedExt = detected.value.substring(6); // Remove 'image/'
final base64Size = (originalSize * 4 / 3).ceil();
// Check for oversized PNG dimensions
bool overDim = false;
if (imageBuffer.length >= 24 &&
imageBuffer[0] == 0x89 &&
imageBuffer[1] == 0x50 &&
imageBuffer[2] == 0x4E &&
imageBuffer[3] == 0x47) {
final w = _readUint32BE(imageBuffer, 16);
final h = _readUint32BE(imageBuffer, 20);
overDim = w > imageMaxWidth || h > imageMaxHeight;
}
if (base64Size <= apiImageMaxBase64Size && !overDim) {
_logEvent('tengu_image_resize_fallback', {
'original_size_bytes': originalSize,
'base64_size_bytes': base64Size,
'error_type': errorType.code,
});
return ResizeResult(buffer: imageBuffer, mediaType: normalizedExt);
}
throw ImageResizeError(
overDim
? 'Unable to resize image -- dimensions exceed the '
'${imageMaxWidth}x${imageMaxHeight}px limit and image processing failed. '
'Please resize the image to reduce its pixel dimensions.'
: 'Unable to resize image (${formatFileSize(originalSize)} raw, '
'${formatFileSize(base64Size)} base64). The image exceeds the 5MB API '
'limit and compression failed. Please resize the image manually '
'or use a smaller image.',
);
}
}