Search code examples
imagefluttercanvasjpegblend

Flutter - Save BlendMode to Image


I'm trying to implement an UI where the user can edit and apply effects to an uploaded image, and want to save the BlendMode merged to the image. It's possible to save the result of the blended image or apply it using the Canvas?

There are some packages that apply some specific filters, but I want something more customizable for the end user.

I already saw some examples of how to implement Canvas to draw images, but can't figure it out how to use to load an image an apply the blend related in the docs. Anyone could give an example?

UPDATED:

For who has the same question, bellow follows the code with how to save a image from canvas to a file with blendMode applied. But I still haven't the result expected. The quality of the image generated isn't the same as the original image, neither the blend seems to be the blend that i've applied. And i can't save as jpg, just as png file.

So, how can i load an image, apply a blend with canvas and save as a jpg file, without losing quality?

CODE:

const kCanvasSize = 200.0;

class CanvasImageToFile {
  CanvasImageToFile._();
  static final instance = CanvasImageToFile._();

  ByteData _readFromFile(File file) {
    // File file = getSomeCorrectFile();
    Uint8List bytes = file.readAsBytesSync();
    return ByteData.view(bytes.buffer);
  }

  Future<File> _writeToFile(ByteData data) async {
    String dir = (await getTemporaryDirectory()).path;
    String filePath = '$dir/tempImage.jpg';
    final buffer = data.buffer;
    return new File(filePath).writeAsBytes(
        buffer.asUint8List(data.offsetInBytes, data.lengthInBytes));
  }

  Future<ui.Image> _loadImageSource(File imageSource) async {
    // ByteData data = await rootBundle.load(asset);
    ByteData data = _readFromFile(imageSource);
    ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List());
    ui.FrameInfo fi = await codec.getNextFrame();
    return fi.image;
  }

  Future<File> generateImage(File imageSource) async {
    File imageResult;
    ui.Image image;
    await _loadImageSource(imageSource).then((value) {
      image = value;
    });
    if (image != null) {
      final recorder = ui.PictureRecorder();
      var rect =
          Rect.fromPoints(Offset(0.0, 0.0), Offset(kCanvasSize, kCanvasSize));
      final canvas = Canvas(recorder, rect);

      Size outputSize = rect.size;
      Paint paint = new Paint();

      //OVERLAY - BlendMode uses the previously drawn content as a mask
      paint.blendMode = BlendMode.colorBurn;
      paint.color = Colors.red;
      // paint.colorFilter = ColorFilter.mode(Colors.blue, BlendMode.colorDodge);
      // paint = Paint()..color = Colors.red;
      // paint = Paint()..blendMode = BlendMode.multiply;

      //Image
      Size inputSize = Size(image.width.toDouble(), image.height.toDouble());
      final FittedSizes fittedSizes =
          applyBoxFit(BoxFit.cover, inputSize, outputSize);
      final Size sourceSize = fittedSizes.source;
      final Rect sourceRect =
          Alignment.center.inscribe(sourceSize, Offset.zero & inputSize);

      canvas.saveLayer(rect, paint);
      canvas.drawImageRect(
          image, sourceRect, rect, paint);
      canvas.restore();

      final picture = recorder.endRecording();
      final img = await picture.toImage(200, 200);
      final byteData = await img.toByteData(format: ImageByteFormat.png);

      await _writeToFile(byteData).then((value) {
        imageResult = value;
      });
      return imageResult;
    }

Solution

  • After some research e some adjust at decoding image from png to rawUnmodified in my previous code using (Bitmap package), i could save the image with the original format (jpg) and achieved what i wanted. If there's anyone who have the same question, bellow follows the code to load an image with canvas, apply a blend and write to a file with the same quality:

    Future<File> generateImage(
          File imageSource, Color color, BlendMode blendMode) async {
        File imageResult;
        ui.Image image;
        await _loadImageSource(imageSource).then((value) {
          image = value;
        });
        if (image != null) {
          final recorder = ui.PictureRecorder();
          var rect = Rect.fromPoints(Offset(0.0, 0.0),
              Offset(image.width.toDouble(), image.height.toDouble()));
          final canvas = Canvas(recorder, rect);
    
          Size outputSize = rect.size;
          Paint paint = new Paint();
    
          //OVERLAY - BlendMode uses the previously drawn content as a mask
          // paint.blendMode = blendMode;
          // paint.color = color;
          paint.colorFilter = ColorFilter.mode(color, blendMode);
    
          //Image
          Size inputSize = Size(image.width.toDouble(), image.height.toDouble());
          final FittedSizes fittedSizes =
              applyBoxFit(BoxFit.contain, inputSize, outputSize);
          final Size sourceSize = fittedSizes.source;
          final Rect sourceRect =
              Alignment.center.inscribe(sourceSize, Offset.zero & inputSize);
    
          canvas.drawImageRect(image, sourceRect, rect, paint);
    
          final picture = recorder.endRecording();
          final img = await picture.toImage(image.width, image.height);
    
          ByteData byteData =
              await img.toByteData(format: ui.ImageByteFormat.rawUnmodified);
          Bitmap bitmap = Bitmap.fromHeadless(
              image.width, image.height, byteData.buffer.asUint8List());
          Uint8List headedIntList = bitmap.buildHeaded();
    
          await _writeToFile(headedIntList.buffer.asByteData()).then((value) {
            imageResult = value;
          });
          return imageResult;
        }
      }