Dart/Flutter에서 이미지를 자르고 압축하는 방법

21029 단어 flutterdart
불필요한 이미지 부분을 제거하여 대역폭을 절약하려는 경우 앱에서 이미지 자르기가 필요합니다. 예를 들어 썸네일을 업로드하고 싶지만 크기가 크고 고품질 이미지가 너무 커지는 것을 원하지 않습니다.



그래서 저는 Flutter에서 이미지를 자르고 압축하는 플러그인을 찾기 시작했고 이를 달성하기 위한 꽤 좋은 플러그인이 있지만 이미지를 자르는 다른 방법을 선호했습니다. 여기서는 UI 없이 백그라운드에서 이미지 자르기에 대해 이야기하겠습니다. 우리는 항상 사용자에게 이미지를 자르라고 요청하지 않으며 때로는 사용자의 상호 작용이나 의도 없이 직접 수행해야 합니다.

다양한 파일 형식의 이미지를 조작하기 위해 dart 패키지인 image 을 사용할 것입니다. 이 패키지에는 이미 copyCrop() , copyCropCircle() , copyResize()copyResizeCropSquare() 가 있지만 작물을 중앙에 배치해야 할 수도 있습니다. 이제 센터 크롭을 해봅시다.

자를 직사각형 계산





먼저 원하는 종횡비의 자르기에 대한 직사각형을 계산해야 합니다. 우리는 자르기 사각형이 항상 원본 이미지 안에 있기를 원합니다. fit: BoxFit.cover를 사용하여 이미지 위젯에서와 유사한 결과를 얻으려고 합니다.



/// Get Crop Rectangle from given sizes
Rectangle<int> getCropRect({
  required int sourceWidth,
  required int sourceHeight,
  required double aspectRatio,
}) {
  var left = 0;
  var top = 0;
  var width = sourceWidth;
  var height = sourceHeight;

  if (aspectRatio < sourceWidth / sourceHeight) {
    // crop horizontally, from width
    width = (sourceHeight * aspectRatio).toInt(); // set new cropped width
    left = (sourceWidth - width) ~/ 2;
  } else if (aspectRatio > sourceWidth / sourceHeight) {
    // crop vertically, from height
    height = sourceWidth ~/ aspectRatio; // set new cropped height
    top = (sourceHeight - height) ~/ 2;
  }
  // else source and destination have same aspect ratio

  return Rectangle<int>(left, top, width, height);
}


센터 크롭



이제 이것을 사용하여 이미지를 중앙 자르기할 수 있습니다. image 패키지를 가져오는 것을 잊지 마십시오.

import 'package:image/image.dart' as img;

/// Center crop the source image with given aspectRatio
img.Image centerCrop(img.Image source, double aspectRatio) {
  final rect = getCropRect(
      sourceWidth: source.width,
      sourceHeight: source.height,
      aspectRatio: aspectRatio);

  return img.copyCrop(source, rect.left, rect.top, rect.width, rect.height);
}


크기 조정



쉬웠지만 이미지 크기를 조정하고 더 작은 이미지를 원하지 않습니까?

/// Crops and resize the image with given aspectRatio and width
img.Image cropAndResize(img.Image src, double aspectRatio, int width) {
  final cropped = centerCrop(src, aspectRatio);
  final croppedResized = img.copyResize(
    cropped,
    width: width,
    interpolation: img.Interpolation.average,
  );
  return croppedResized;
}


입력 이미지 크기가 원하는 이미지 크기보다 작으면 어떻게 됩니까? 입력 이미지를 업스케일링합니다. 이를 원하지 않으면 언제든지 다음과 같이 코드를 수정할 수 있습니다.

    cropped,
    width: width,
    interpolation: img.Interpolation.average,
  );
  if (cropped.width <= width) {
    // do not resize if cropped image is smaller than desired image size
    // to avoid upscaling
    return cropped;
  }
  return croppedResized;
}


여기에서 자신에게 가장 적합한 interpolation를 변경할 수 있습니다. 이미지를 크게 축소하면 좋을 것 같아서 average를 사용했습니다. 자세한 내용은 여기에서 GitHub 문제를 확인할 수 있습니다. https://github.com/brendan-duncan/image/issues/23

이 모든 방법을 결합




Future<File> cropAndResizeFile({
  required File file,
  required double aspectRatio,
  required int width,
  int quality = 100,
}) async {
  final tempDir = await getTemporaryDirectory();
  final destPath = path.join(
    tempDir.path,
    path.basenameWithoutExtension(file.path) + '_compressed.jpg',
  );

  return compute<_CropResizeArgs, File>(
      _cropAndResizeFile,
      _CropResizeArgs(
        sourcePath: file.path,
        destPath: destPath,
        aspectRatio: aspectRatio,
        width: width,
        quality: quality,
      ));
}

class _CropResizeArgs {
  final String sourcePath;
  final String destPath;
  final double aspectRatio;
  final int width;
  final int quality;
  _CropResizeArgs({
    required this.sourcePath,
    required this.destPath,
    required this.aspectRatio,
    required this.width,
    required this.quality,
  });
}

Future<File> _cropAndResizeFile(_CropResizeArgs args) async {
  final image =
      await img.decodeImage(await File(args.sourcePath).readAsBytes());

  if (image == null) throw Exception('Unable to decode image from file');

  final croppedResized = cropAndResize(image, args.aspectRatio, args.width);
  // Encoding image to jpeg to compress the image. 
  final jpegBytes = img.encodeJpg(croppedResized, quality: args.quality);

  final croppedImageFile = await File(args.destPath).writeAsBytes(jpegBytes);
  return croppedImageFile;
}

_CropResizeArgs를 사용하는 이유가 궁금할 수 있습니다. compute 함수에 데이터를 전달하는 데 사용됩니다. 그리고 compute 함수를 사용하는 이유는 무엇입니까? 기본 격리에서 리소스 집약적인 작업을 처리하면 앱이 버벅거립니다. 버벅거림이라는 용어를 모른다면 더 간단한 용어로 지연과 무응답입니다. 이미지 선택의 경우 버벅거림으로 인해 이미지를 선택한 후 검은색 화면이 표시됩니다. 메인 격리는 이미지를 선택한 후 모든 이미지 디코딩, 자르기, 크기 조정 및 인코딩을 처리해야 하기 때문입니다. 여기에서 자세한 내용을 볼 수 있습니다. https://dart.dev/guides/language/concurrency , https://docs.flutter.dev/cookbook/networking/background-parsing .

그걸 써




TextButton(
      onPressed: () async {
        // resizing image with image picker decreases quality
        final _pickedFile = await picker.pickImage(
          source: ImageSource.gallery,
        );

        // if user cancels file picking process do nothing
        if (_pickedFile == null) return;

        final length = await _pickedFile!.length() / 1024;
        log('Picked File Size in KB $length');

        await cropAndResizeFile(
          file: File(_pickedFile!.path),
          aspectRatio: 1.6,
          width: 800,
          quality: 80,
        ).then((value) async {
          _croppedFile = value;

          final length = await value.length() / 1024;
          log('Compressed File Size in KB $length');
        });
      },
      child: const Text('Pick an image'),
    )


종횡비, 너비 및 품질을 변경하면 주어진 품질 및 압축으로 가운데 잘린 이미지를 얻을 수 있습니다. 또한 이미지 선택기 플러그인을 사용하여 이미지의 크기를 조정(가로 세로 비율 유지)하고 압축할 수 있습니다. 크기를 조정하고 압축한 후 이미지 품질이 매우 나빠졌기 때문에 여기서는 사용하지 않았습니다. 또한 jpeg를 디코딩하고 다시 인코딩할 때마다 일부 데이터가 손실되어 이미지 품질이 저하됩니다. 이를 방지하기 위해 원본 이미지를 한 번 디코딩하고 모든 작업을 수행한 다음 최종 이미지로 다시 인코딩합니다.

Source

좋은 웹페이지 즐겨찾기