multiavatar function

String multiavatar(
  1. String string, {
  2. bool transparentBackground = false,
  3. GenderType gender = GenderType.neutral,
})

Returns the SVG markup for the avatar derived from string.

The same string always produces the same avatar. Returns an empty string when string is empty.

Set transparentBackground to true to omit the circular background.

gender constrains the mouth and top (hair) parts. GenderType.neutral (the default) applies no filtering. A [m] or [f] prefix on string overrides gender and is stripped before hashing, so multiavatar('[f]ada') and multiavatar('ada', gender: GenderType.female) are equivalent.

Implementation

String multiavatar(String string,
    {bool transparentBackground = false,
    GenderType gender = GenderType.neutral}) {
  string += '';

  final pre = string.length >= 3 ? string.substring(0, 3) : '';
  if (pre == '[f]') {
    gender = GenderType.female;
    string = string.substring(3);
  } else if (pre == '[m]') {
    gender = GenderType.male;
    string = string.substring(3);
  }

  String hash = '';
  if (string.length == 0) return hash;

  List<int> bytes1 = utf8.encode(string);
  Digest sha256Hash = sha256.convert(bytes1);
  String sha256Numbers =
      sha256Hash.toString().replaceAll(RegExp(r'[^0-9]'), '');

  hash = sha256Numbers.substring(0, 12);

  Map<dynamic, dynamic> p = {};

  p['env'] = hash[0] + '' + hash[1];
  p['env'] = ((47 / 100) * int.parse(p['env'])).round().toString() + '';

  p['clo'] = hash[2] + '' + hash[3];
  p['clo'] = ((47 / 100) * int.parse(p['clo'])).round().toString() + '';

  p['head'] = hash[4] + '' + hash[5];
  p['head'] = ((47 / 100) * int.parse(p['head'])).round().toString() + '';

  p['mouth'] = hash[6] + '' + hash[7];
  p['mouth'] = ((47 / 100) * int.parse(p['mouth'])).round().toString() + '';

  p['eyes'] = hash[8] + '' + hash[9];
  p['eyes'] = ((47 / 100) * int.parse(p['eyes'])).round().toString() + '';

  p['top'] = hash[10] + '' + hash[11];
  p['top'] = ((47 / 100) * int.parse(p['top'])).round().toString() + '';

  for (var part in p.keys) {
    var nr = p[part];

    if (int.parse(nr) > 31) {
      nr = (int.parse(nr) - 32).toString() + '';
      if (nr.length == 1) nr = '0' + nr;
      p[part] = nr + 'C';
    } else if (int.parse(nr) > 15) {
      nr = (int.parse(nr) - 16).toString() + '';
      if (nr.length == 1) nr = '0' + nr;
      p[part] = nr + 'B';
    } else {
      if ((nr + '').length == 1)
        p[part] = '0' + nr + 'A';
      else
        p[part] = nr + 'A';
    }
  }

  // Gender-exclusive parts: if a part belongs to the opposite gender, swap it.
  const femaleMouth = ['02A', '02B', '02C'];
  const femaleTop = ['02A', '02C'];
  const maleMouth = [
    '04A', '04B', '04C', '07A', '07B', '07C', '10A', '10B', '11A', '11B',
    '11C', '12A', '12B', '12C', '13A', '13B', '13C', '14A', '14B', '14C'
  ];
  const maleTop = ['04A', '04B', '04C', '05C', '10A'];

  String nextPart(String cur, List<String> exclude) {
    var num = int.parse(cur.substring(0, 2));
    final suffix = cur[2];
    String next;
    do {
      num += 1;
      if (num > 15) num = 0;
      next = (num < 10 ? '0' : '') + num.toString() + suffix;
    } while (exclude.contains(next));
    return next;
  }

  if (gender == GenderType.male) {
    if (femaleMouth.contains(p['mouth']))
      p['mouth'] = nextPart(p['mouth'], femaleMouth);
    if (femaleTop.contains(p['top'])) p['top'] = nextPart(p['top'], femaleTop);
  } else if (gender == GenderType.female) {
    if (maleMouth.contains(p['mouth'])) p['mouth'] = '02' + p['mouth'][2];
    if (maleTop.contains(p['top'])) p['top'] = nextPart(p['top'], maleTop);
  }

  String getFinal(part, partV, theme) {
    List<String?>? colors = _themes[partV]?[theme]?[part];
    String? svgString = _sP[partV]?[part];

    String regex = r"(#.*?;)";
    String resultFinal = svgString ?? "";

    final List<String?>? result = RegExp(regex)
        .allMatches(svgString ?? "")
        .toList()
        .map((e) => e.group(1))
        .toList();
    // print(result);

    for (var i = 0; i < (result?.length ?? 0);  i++) {
      // print("change: ${result[i]} ===> with: ${colors[i]}");
      resultFinal =
          resultFinal.replaceFirst(result?[i] ?? "", (colors?[i] ?? "") + ';');
    }

    return resultFinal;
  }

  Map<dynamic, dynamic> _final = {};

  for (var part in p.keys) {
    var partV = p[part].substring(0, 2);
    var theme = p[part].substring(2, 3);

    _final[part] = getFinal(part, partV, theme);
  }

  if (transparentBackground) _final['env'] = '';

  return (_svgStart +
      _final['env'] +
      _final['head'] +
      _final['clo'] +
      _final['top'] +
      _final['eyes'] +
      _final['mouth'] +
      _svgEnd);
}