flutter / flutter/flutter

[vector_graphics] Updating colors and shaders at runtime

Open
#158,859 2 comments 10 reactions 0 assignees View on GitHub
c: new feature c: proposal p: vector_graphics package team-engine
Dominant language
Dart
Stars
179k
Forks
31.1k
PR merge metrics
PR metrics pending

Description

_Imported from https://github.com/dnfield/vector_graphics/issues/213_

Original report by @aloisdeniel on 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 :

```dart
class VectorStylesOverride {
const VectorStylesOverride({
this.shaders = const {},
this.paints = const {},
});
final Map shaders;
final Map paints;
}
```

Add a property to `FlutterVectorGraphicsListener` :

```dart
final VectorStylesOverride? styleOverrides;
```

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

```dart

@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? 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](https://github.com/dnfield/vector_graphics/assets/7687231/b5eb88c4-083c-449f-9143-e77cce6bc9a8)

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.

---
Comment by @aloisdeniel on Aug 31, 2023
Here is an example of such a listener if `FlutterVectorGraphicsListener ` would have a `raw` public constructor instead of `_`.

```dart

class OverridesFlutterVectorGraphicsListener
extends FlutterVectorGraphicsListener {
factory OverridesFlutterVectorGraphicsListener({
required Map 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 colorOverrides;

@override
void onLinearGradient(
double fromX,
double fromY,
double toX,
double toY,
Int32List colors,
Float32List? offsets,
int tileMode,
int id,
) {
final colorValues = [];
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);
}
}

```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.