maybeResizeAndDownsampleImageBuffer method

Future<ResizeResult> maybeResizeAndDownsampleImageBuffer(
  1. Uint8List imageBuffer,
  2. int originalSize,
  3. String ext
)

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.',
    );
  }
}