How to visualize masks

Example code how to visualize SVG masks from Face Skin Analysis 3.0 (URLs should be received from API/Webhook separately).

Code

import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:http/http.dart' as http;
import 'dart:typed_data';

void main() => runApp(const NetworkOverlayApp());

class NetworkOverlayApp extends StatefulWidget {
  const NetworkOverlayApp({super.key});
  @override
  State<NetworkOverlayApp> createState() => _NetworkOverlayAppState();
}

class _NetworkOverlayAppState extends State<NetworkOverlayApp> {
  // Replace with your signed URLs:
  static const photoUrl = '';
  static const svgUrl   = '';


  late Future<_Payload> _future;

  @override
  void initState() {
    super.initState();
    _future = _load();
  }

  Future<_Payload> _load() async {
    // 1) Fetch image bytes & decode intrinsic size
    final imgResp = await http.get(Uri.parse(photoUrl));
    if (imgResp.statusCode != 200) throw Exception('Image load failed');
    final imgBytes = Uint8List.fromList(imgResp.bodyBytes);
    final imgCodec = await ui.instantiateImageCodec(imgBytes);
    final frame = await imgCodec.getNextFrame();
    final imgW = frame.image.width.toDouble();
    final imgH = frame.image.height.toDouble();

    // 2) Fetch SVG string (optional: strip <style> if flutter_svg chokes on it)
    final svgRaw = await http.read(Uri.parse(svgUrl));
    final svg = _sanitizeSvg(svgRaw); // remove <style>...</style> minimally

    return _Payload(
      imageBytes: imgBytes,
      imageW: imgW,
      imageH: imgH,
      svgString: svg,
    );
  }

  // Minimal sanitizer for CSS-in-SVG issues:
  String _sanitizeSvg(String raw) {
    final style = RegExp(r'<style[^>]*>[\s\S]*?<\/style>', multiLine: true);
    return raw.replaceAll(style, '');
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        backgroundColor: Colors.grey[200],
        body: Center(
          child: FutureBuilder<_Payload>(
            future: _future,
            builder: (context, snap) {
              if (snap.connectionState != ConnectionState.done) {
                return const SizedBox.square(
                  dimension: 48,
                  child: CircularProgressIndicator(),
                );
              }
              if (snap.hasError || !snap.hasData) {
                return const Icon(Icons.error, size: 32);
              }

              final p = snap.data!;
              // 3) Paint both layers in the SAME intrinsic box (imgW × imgH)
              final overlay = SizedBox(
                width: p.imageW,
                height: p.imageH,
                child: Stack(
                  fit: StackFit.expand,
                  children: [
                    // Use Image.memory to avoid re-downloading
                    Image.memory(
                      p.imageBytes,
                      fit: BoxFit.fill, // map to the box exactly
                    ),
                    SvgPicture.string(
                      p.svgString,
                      // Crucial: also fill the same box so viewBox maps 1:1
                      fit: BoxFit.fill,
                      // If your SVG paths already have fills, don't tint:
                      // colorFilter: const ColorFilter.mode(Colors.white70, BlendMode.srcIn),
                    ),
                  ],
                ),
              );

              // 4) Now scale that whole box together for display
              return ClipRRect(
                borderRadius: BorderRadius.circular(12),
                child: FittedBox(
                  fit: BoxFit.contain, // or BoxFit.cover if you want cropping
                  child: overlay,
                ),
              );
            },
          ),
        ),
      ),
    );
  }
}

class _Payload {
  final Uint8List imageBytes;   // make this Uint8List, not List<int>
  final double imageW;
  final double imageH;
  final String svgString;
  _Payload({
    required this.imageBytes,
    required this.imageW,
    required this.imageH,
    required this.svgString,
  });
}

Last updated

Was this helpful?