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

Updating colors and shaders at runtime #213

Open
aloisdeniel opened this issue Aug 24, 2023 · 1 comment
Open

Updating colors and shaders at runtime #213

aloisdeniel opened this issue Aug 24, 2023 · 1 comment

Comments

@aloisdeniel
Copy link

aloisdeniel commented Aug 24, 2023

One of the benefits of vector graphics is their ability to be updated at runtime. It is pretty common, for example, to change icon's color regarding the brightness or accent color.

I know that ColorMapper allows us to do it with SVG, but it would be even better to allow this for binary vector graphics too. My idea is that I will pre-compile all my SVG assets as binary content to make it more efficient to load.

Therefore it would be great if we could override the color table directly in FlutterVectorGraphicsListener to update several colors of our vector images.

I would probably create a class like :

class VectorStylesOverride {
     const VectorStylesOverride({
         this.shaders = const <int,Shader>{},
         this.paints = const <int,Paint>{},
     });
     final Map<int,Shader> shaders; 
     final Map<int,Paint> paints; 
}

Add a property to FlutterVectorGraphicsListener :

final VectorStylesOverride? styleOverrides;

And then in the code, when we inserting a paint or shader, we first look at overrides :

  @override
  void onPaintObject({
    required int color,
    required int? strokeCap,
    required int? strokeJoin,
    required int blendMode,
    required double? strokeMiterLimit,
    required double? strokeWidth,
    required int paintStyle,
    required int id,
    required int? shaderId,
  }) {
    assert(_paints.length == id, 'Expect ID to be ${_paints.length}');

    /// If we add an override we add it to the paints instead
    final Map<int, Paint>? overrides = _styleOverrides?.paints;
    if (overrides != null) {
      final Paint? override = overrides[id];
      if (override != null) {
        _paints.add(override);
        return;
      }
    }
    // ...

Also since FlutterVectorGraphicsListener constructor's is private I can't even override this method in a subclass.

image

Another way of doing this would be to update the binary data from the overrides before reading it, like the ColorMapper for SVG.

When using an image we probably would have to create a unique id when giving overrides to cache this alternate version.

@aloisdeniel aloisdeniel changed the title Updating colors at runtime Updating colors and shaders at runtime Aug 24, 2023
@aloisdeniel
Copy link
Author

Here is an example of such a listener if FlutterVectorGraphicsListener would have a raw public constructor instead of _.

class OverridesFlutterVectorGraphicsListener
    extends FlutterVectorGraphicsListener {
  factory OverridesFlutterVectorGraphicsListener({
    required Map<int, int> colorOverrides,
    int id = 0,
    Locale? locale,
    TextDirection? textDirection,
    bool clipViewbox = true,
    @visibleForTesting
    DefaultPictureFactory pictureFactory = const DefaultPictureFactory(),
  }) {
    final PictureRecorder recorder = pictureFactory.createPictureRecorder();
    return OverridesFlutterVectorGraphicsListener.raw(
      id,
      pictureFactory,
      recorder,
      pictureFactory.createCanvas(recorder),
      locale,
      textDirection,
      clipViewbox,
      colorOverrides,
    );
  }

  OverridesFlutterVectorGraphicsListener.raw(
    int id,
    DefaultPictureFactory pictureFactory,
    PictureRecorder recorder,
    Canvas canvas,
    Locale? locale,
    TextDirection? textDirection,
    bool clipViewbox,
    this.colorOverrides,
  ) : super.raw(
          id,
          pictureFactory,
          recorder,
          canvas,
          locale,
          textDirection,
          clipViewbox,
        );

  final Map<int, int> colorOverrides;

  @override
  void onLinearGradient(
    double fromX,
    double fromY,
    double toX,
    double toY,
    Int32List colors,
    Float32List? offsets,
    int tileMode,
    int id,
  ) {
    final colorValues = <int>[];
    for (var color in colors) {
      final replacedColor = colorOverrides[_colorValue(color)];
      colorValues.add(replacedColor ?? color);
    }
    super.onLinearGradient(
      fromX,
      fromY,
      toX,
      toY,
      Int32List.fromList(colorValues),
      offsets,
      tileMode,
      id,
    );
  }

  @override
  void onPaintObject({
    required int color,
    required int? strokeCap,
    required int? strokeJoin,
    required int blendMode,
    required double? strokeMiterLimit,
    required double? strokeWidth,
    required int paintStyle,
    required int id,
    required int? shaderId,
  }) {
    /// If we add an override we add it to the paints instead

    final int? override = colorOverrides[_colorValue(color)];
    if (override != null) {
      return super.onPaintObject(
        id: id,
        color: override,
        strokeCap: strokeCap,
        strokeJoin: strokeJoin,
        blendMode: blendMode,
        strokeMiterLimit: strokeMiterLimit,
        strokeWidth: strokeWidth,
        paintStyle: paintStyle,
        shaderId: shaderId,
      );
    }

    return super.onPaintObject(
      id: id,
      color: color,
      strokeCap: strokeCap,
      strokeJoin: strokeJoin,
      blendMode: blendMode,
      strokeMiterLimit: strokeMiterLimit,
      strokeWidth: strokeWidth,
      paintStyle: paintStyle,
      shaderId: shaderId,
    );
  }

  final _colorData = ByteData(4);

  int _colorValue(int value) {
    _colorData.setInt32(0, value);
    return _colorData.getUint32(0);
  }
}

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

1 participant