Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[flutter_svg] After methods deprecation, how can I convert SVG to the bitmap without Widget? #158728

Open
stuartmorgan opened this issue Nov 13, 2024 · 0 comments
Labels
p: flutter_svg The Flutter SVG drawing packages package flutter/packages repository. See also p: labels.

Comments

@stuartmorgan
Copy link
Contributor

Imported from dnfield/flutter_svg#858

Original report by @arsenioo on Feb 12, 2023

Hello,

For versions 1.1.6 and below I used the following code to convert SVG to Image:

final root = await svg.fromSvgString(...);
final picture = root.toPicture(size: size);
final image = await picture.toImage(...);

Now this approach is deprecated, but I still need to get a bitmap from SVG bypassing Widget. Any idea how to accomplish this?

Thank you!


Comment by @dnfield on Feb 13, 2023
Looks like this got lost in the readme updates.

I'll add it back. In the mean time here is a code snippet that should work:

final pictureInfo = await vg.loadPicture(SvgStringLoader(...);
final image = await pictureInfo.picture.toImage(...);

Comment by @arsenioo on Feb 13, 2023
Thank you very much for the prompt response, now it works!

Although I still have some issues when I switch from 1.1.6 to 2.0.1 which disappear when switch back, I'm still not ready to blame flutter_svg in this, it is probably my bug. If not, I'll reopen this issue with further details o create a new one.


Comment by @arsenioo on Feb 14, 2023
A quick follow up. In order to render SVG into destination bitmap dimensions I had to write the following code:

  final ui.PictureRecorder recorder = ui.PictureRecorder();
  final ui.Canvas canvas = ui.Canvas(recorder);

  canvas.scale(dstWidth / pictureInfo.size.width, dstHeight / pictureInfo.size.height);
  canvas.drawPicture(pictureInfo.picture);
  final ui.Picture scaledPicture = recorder.endRecording();

  final image = await scaledPicture.toImage(dstWidth, dstHeight);

Is there any chance to avoid this intermediate step and get the desired picture size after loadPicture? If not, may be it would be worthful to make some public method for this or additional Size parameter? Just because SVG -> scale -> bitmap case is something essential and the "scale" part should not lead to reinvent the wheel :)

Actually, that's why my code worked fine in 1.1.6, so for 2.x it is also a question of the backward compatibility.

Thank you!


Comment by @NachiketaVadera on Feb 16, 2023
This works but would love to get fromSvgString back


Comment by @androidseb on Feb 25, 2023

A quick follow up. In order to render SVG into destination bitmap dimensions I had to write the following code:

  final ui.PictureRecorder recorder = ui.PictureRecorder();
  final ui.Canvas canvas = ui.Canvas(recorder);

  canvas.scale(dstWidth / pictureInfo.size.width, dstHeight / pictureInfo.size.height);
  canvas.drawPicture(pictureInfo.picture);
  final ui.Picture scaledPicture = recorder.endRecording();

  final image = await scaledPicture.toImage(dstWidth, dstHeight);

This only really "works" if you're scaling down your SVG image, but if you're looking to scale it up, you will lose quality and your picture will become pixelated. I have not found a way to achieve this without quality loss at this time...

It seems like the only way to achieve this would be with a library change, maybe adding a size parameter to the SvgStringLoader object constructor? Something like this:

SvgStringLoader('<xml... </xml>', size: Size(500,500))

or

final pictureInfo = await vg.loadPicture(SvgStringLoader(...), size: Size(500,500));

Or maybe it is possible through some other way that I have not found? Anybody found a way to achieve this without losing picture quality when scaling up the SVG image?


Comment by @revever on Feb 25, 2023

I'll add it back. In the mean time here is a code snippet that should work:

final pictureInfo = await vg.loadPicture(SvgStringLoader(...);
final image = await pictureInfo.picture.toImage(...);

If the original svg view is too large, the generatd image may take a lot of memory. It will be nice to have a size option when loading the picture.


Comment by @sgehrman on Feb 27, 2023
same issue. why isn't it simple to convert an svg to an image with a size? Do we have to edit the svg to fix the size?


Comment by @sgehrman on Feb 27, 2023
jovial_svg works well:

import 'package:jovial_svg/jovial_svg.dart';

  static Future<Uint8List> svgToPng({
    required String svg,
    ui.Size? scaleTo,
  }) async {
    try {
      final ScalableImage si = ScalableImage.fromSvgString(svg);

      await si.prepareImages();
      final vpSize = si.viewport;

      final recorder = ui.PictureRecorder();
      final ui.Canvas c = ui.Canvas(recorder);

      if (scaleTo != null) {
        c.scale(scaleTo.width / vpSize.width, scaleTo.height / vpSize.height);
      }
      si.paint(c);
      si.unprepareImages();

      final size = scaleTo ?? ui.Size(vpSize.width, vpSize.height);
      final ui.Picture pict = recorder.endRecording();

      final ui.Image rendered =
          await pict.toImage(size.width.round(), size.height.round());

      final ByteData? bd = await rendered.toByteData(
        format: ui.ImageByteFormat.png,
      );

      pict.dispose();
      rendered.dispose();

      if (bd != null) {
        return bd.buffer.asUint8List();
      }
    } catch (err) {
      print('svgToPngBytes: Error = $err');
    }

    return Uint8List(0);
  }


Comment by @NachiketaVadera on Mar 20, 2023
Also, vg.loadPicture seems to require build context. Is there any reason why we can have fromSvgString back in?


Comment by @dnfield on Mar 20, 2023

Also, vg.loadPicture seems to require build context. Is there any reason why we can have fromSvgString back in?

Build context is nullable.


Comment by @FaFre on Jun 2, 2023

A quick follow up. In order to render SVG into destination bitmap dimensions I had to write the following code:

  final ui.PictureRecorder recorder = ui.PictureRecorder();
  final ui.Canvas canvas = ui.Canvas(recorder);

  canvas.scale(dstWidth / pictureInfo.size.width, dstHeight / pictureInfo.size.height);
  canvas.drawPicture(pictureInfo.picture);
  final ui.Picture scaledPicture = recorder.endRecording();

  final image = await scaledPicture.toImage(dstWidth, dstHeight);

This only really "works" if you're scaling down your SVG image, but if you're looking to scale it up, you will lose quality and your picture will become pixelated. I have not found a way to achieve this without quality loss at this time...

It seems like the only way to achieve this would be with a library change, maybe adding a size parameter to the SvgStringLoader object constructor? Something like this:

SvgStringLoader('<xml... </xml>', size: Size(500,500))

or

final pictureInfo = await vg.loadPicture(SvgStringLoader(...), size: Size(500,500));

Or maybe it is possible through some other way that I have not found? Anybody found a way to achieve this without losing picture quality when scaling up the SVG image?

I was assuming the same, but I found out this is not the case.

Taken this SVG icon with the "size" of 24x24:

<svg xmlns="http://www.w3.org/2000/svg" id="mdi-ab-testing" viewBox="0 0 24 24"><path d="M4 2A2 2 0 0 0 2 4V12H4V8H6V12H8V4A2 2 0 0 0 6 2H4M4 4H6V6H4M22 15.5V14A2 2 0 0 0 20 12H16V22H20A2 2 0 0 0 22 20V18.5A1.54 1.54 0 0 0 20.5 17A1.54 1.54 0 0 0 22 15.5M20 20H18V18H20V20M20 16H18V14H20M5.79 21.61L4.21 20.39L18.21 2.39L19.79 3.61Z" /></svg>

Scaled up to 512x512:

canvas.scale(512 / pic.size.width, 512 / pic.size.height);
canvas.drawPicture(pic.picture);

Produces following sharp picture:
test

So scaling the canvas seems perfectly valid and the right thing to do. Probably the Viewport Size is just for the SVG internal commands that also must be in a certain coordinate space, but doesn't apply to the actual SVG since this will be always salable through its vectors.


Comment by @androidseb on Oct 2, 2023
I struggled to get this working, but @FaFre's response helped me figure it out.

I'll post my full code sample here in case it helps - I wanted to convert an SVG string to PNG bytes, but you can adapt the code and grab intermediary results to suit your needs:

// With the following imports:
// import 'dart:typed_data';
// import 'dart:ui' as ui;
// import 'package:flutter/widgets.dart';
// import 'package:flutter_svg/flutter_svg.dart';
Future<Uint8List> svgStringToPngBytes(
  // The SVG string
  String svgStringContent,
  // The target width of the output image
  double targetWidth,
  // The target height of the output image
  double targetHeight,
) async {
  final SvgStringLoader svgStringLoader = SvgStringLoader(svgStringContent);
  final PictureInfo pictureInfo = await vg.loadPicture(svgStringLoader, null);
  final ui.Picture picture = pictureInfo.picture;
  final ui.PictureRecorder recorder = ui.PictureRecorder();
  final ui.Canvas canvas = Canvas(recorder, Rect.fromPoints(Offset.zero, Offset(targetWidth, targetHeight)));
  canvas.scale(targetWidth / pictureInfo.size.width, targetHeight / pictureInfo.size.height);
  canvas.drawPicture(picture);
  final ui.Image imgByteData = await recorder.endRecording().toImage(targetWidth.ceil(), targetHeight.ceil());
  final ByteData? bytesData = await imgByteData.toByteData(format: ui.ImageByteFormat.png);
  final Uint8List imageData = bytesData?.buffer.asUint8List() ?? Uint8List(0);
  pictureInfo.picture.dispose();
  return imageData;
}

We get a nicely non-pixelated vector-scaled PNG image out of this, even if the SVG image was smaller than the specified target size.

As mentioned in the 2.0.0 release notes, from what I can see (note it's specific to my setup), the performance has improved significantly: I've benchmarked running that specific function 1000 times on a specific image in debug mode, and what used to take 2 seconds now takes about 1.5 seconds.

I've created a PR to improve the documentation and help people find how to scale the SVG images here. I'm hopeful that with this clarification, we could resolve/close this issue... I hope this helps!

@stuartmorgan stuartmorgan added p: flutter_svg The Flutter SVG drawing packages package flutter/packages repository. See also p: labels. labels Nov 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
p: flutter_svg The Flutter SVG drawing packages package flutter/packages repository. See also p: labels.
Projects
None yet
Development

No branches or pull requests

1 participant