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

After methods deprecation, how can I convert SVG to the bitmap without Widget? #858

Open
arsenioo opened this issue Feb 12, 2023 · 12 comments

Comments

@arsenioo
Copy link

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!

@dnfield
Copy link
Owner

dnfield commented 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(...);

@arsenioo
Copy link
Author

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.

@arsenioo
Copy link
Author

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!

@arsenioo arsenioo reopened this Feb 14, 2023
@NachiketaVadera
Copy link

This works but would love to get fromSvgString back

@androidseb
Copy link

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?

@revever
Copy link

revever commented 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.

@sgehrman
Copy link

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?

@sgehrman
Copy link

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);
  }

@NachiketaVadera
Copy link

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

@dnfield
Copy link
Owner

dnfield commented 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.

@FaFre
Copy link
Contributor

FaFre commented 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.

@androidseb
Copy link

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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants