draw_your_image 0.8.0 copy "draw_your_image: ^0.8.0" to clipboard
draw_your_image: ^0.8.0 copied to clipboard

A Flutter package which enables users to draw with fingers in your designed UI.

example/lib/main.dart

import 'dart:ui' as ui;
import 'dart:math' as math;
import 'package:draw_your_image/draw_your_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';

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

class ShaderDemoApp extends StatelessWidget {
  const ShaderDemoApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Shader Effects Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const ShaderDemoPage(),
    );
  }
}

/// Shader effect types
enum ShaderEffect {
  defaultEffect('Default', 'Standard solid color'),
  linearGradient('Linear', 'Start to end gradient'),
  radialGradient('Radial', 'Center to edge gradient'),
  sweepGradient('Sweep', 'Rotating gradient'),
  rainbow('Rainbow', '7-color gradient'),
  glow('Glow', 'Glowing effect'),
  metallic('Metallic', 'Metallic shine'),
  fragmentShader('Shader', 'Fragment shader tint'),
  multiLayer('Multi-Layer', 'Multiple paint layers');

  const ShaderEffect(this.label, this.description);
  final String label;
  final String description;
}

class ShaderDemoPage extends StatefulWidget {
  const ShaderDemoPage({super.key});

  @override
  State<ShaderDemoPage> createState() => _ShaderDemoPageState();
}

class _ShaderDemoPageState extends State<ShaderDemoPage>
    with SingleTickerProviderStateMixin {
  List<Stroke> _strokes = [];
  ShaderEffect _currentEffect = ShaderEffect.defaultEffect;
  ui.FragmentProgram? _tintProgram;
  late final Ticker _ticker;
  double _time = 0.0;

  void _clear() {
    setState(() => _strokes = []);
  }

  @override
  void initState() {
    super.initState();
    // Load fragment program for tint shader. If loading fails, we'll keep null and
    // fall back to default painter.

    ui.FragmentProgram.fromAsset('shaders/tint.frag').then((p) {
      setState(() => _tintProgram = p);
    });
    _ticker = createTicker((elapsed) {
      setState(() {
        _time = elapsed.inMilliseconds / 1000.0;
      });
    });
    _ticker.start();
  }

  @override
  void dispose() {
    _ticker.stop();
    _ticker.dispose();
    super.dispose();
  }

  /// Calculate bounding box for a stroke
  Rect _getStrokeBounds(Stroke stroke) {
    if (stroke.points.isEmpty) {
      return Rect.zero;
    }

    double minX = stroke.points.first.dx;
    double maxX = stroke.points.first.dx;
    double minY = stroke.points.first.dy;
    double maxY = stroke.points.first.dy;

    for (final point in stroke.points) {
      minX = math.min(minX, point.dx);
      maxX = math.max(maxX, point.dx);
      minY = math.min(minY, point.dy);
      maxY = math.max(maxY, point.dy);
    }

    // Add padding for stroke width
    final padding = stroke.width * 2;
    return Rect.fromLTRB(
      minX - padding,
      minY - padding,
      maxX + padding,
      maxY + padding,
    );
  }

  /// Get stroke painter based on current effect
  List<Paint> _getStrokePainter(Stroke stroke, Size canvasSize) {
    switch (_currentEffect) {
      case ShaderEffect.defaultEffect:
        return _defaultPainter(stroke, canvasSize);
      case ShaderEffect.linearGradient:
        return _linearGradientPainter(stroke, canvasSize);
      case ShaderEffect.radialGradient:
        return _radialGradientPainter(stroke, canvasSize);
      case ShaderEffect.sweepGradient:
        return _sweepGradientPainter(stroke, canvasSize);
      case ShaderEffect.rainbow:
        return _rainbowPainter(stroke, canvasSize);
      case ShaderEffect.glow:
        return _glowPainter(stroke, canvasSize);
      case ShaderEffect.metallic:
        return _metallicPainter(stroke, canvasSize);
      case ShaderEffect.fragmentShader:
        return _fragmentShaderPainter(stroke, canvasSize);
      case ShaderEffect.multiLayer:
        return _multiLayerPainter(stroke, canvasSize);
    }
  }

  /// Default painter - solid color
  List<Paint> _defaultPainter(Stroke stroke, Size canvasSize) {
    return [paintWithDefault(stroke)];
  }

  /// Linear gradient from start to end
  List<Paint> _linearGradientPainter(Stroke stroke, Size canvasSize) {
    final canvasRect = Rect.fromLTWH(0, 0, canvasSize.width, canvasSize.height);
    final _colors = [Colors.blue, Colors.purple, Colors.pink];
    final _stops = List.generate(
      _colors.length,
      (i) => i / (_colors.length - 1),
    );
    final gradient = ui.Gradient.linear(
      canvasRect.topLeft,
      canvasRect.bottomRight,
      _colors,
      _stops,
    );

    return [
      Paint()
        ..shader = gradient
        ..strokeWidth = stroke.width
        ..strokeCap = StrokeCap.round
        ..style = PaintingStyle.stroke,
    ];
  }

  /// Radial gradient from center
  List<Paint> _radialGradientPainter(Stroke stroke, Size canvasSize) {
    final canvasRect = Rect.fromLTWH(0, 0, canvasSize.width, canvasSize.height);
    final center = canvasRect.center;
    final radius = math.max(canvasRect.width, canvasRect.height) / 2;

    final _colors = [Colors.yellow, Colors.orange, Colors.red];
    final _stops = List.generate(
      _colors.length,
      (i) => i / (_colors.length - 1),
    );
    final gradient = ui.Gradient.radial(center, radius, _colors, _stops);

    return [
      Paint()
        ..shader = gradient
        ..strokeWidth = stroke.width
        ..strokeCap = StrokeCap.round
        ..style = PaintingStyle.stroke,
    ];
  }

  /// Sweep gradient (rotating effect)
  List<Paint> _sweepGradientPainter(Stroke stroke, Size canvasSize) {
    final canvasRect = Rect.fromLTWH(0, 0, canvasSize.width, canvasSize.height);
    final center = canvasRect.center;

    final _colors = [
      Colors.red,
      Colors.yellow,
      Colors.green,
      Colors.cyan,
      Colors.blue,
      const Color(0xFFFF00FF), // Magenta
      Colors.red,
    ];
    final _stops = List.generate(
      _colors.length,
      (i) => i / (_colors.length - 1),
    );
    final gradient = ui.Gradient.sweep(center, _colors, _stops);

    return [
      Paint()
        ..shader = gradient
        ..strokeWidth = stroke.width
        ..strokeCap = StrokeCap.round
        ..style = PaintingStyle.stroke,
    ];
  }

  /// Rainbow effect - 7 colors
  List<Paint> _rainbowPainter(Stroke stroke, Size canvasSize) {
    final canvasRect = Rect.fromLTWH(0, 0, canvasSize.width, canvasSize.height);

    final _colors = [
      const Color(0xFFFF0000), // Red
      const Color(0xFFFF7F00), // Orange
      const Color(0xFFFFFF00), // Yellow
      const Color(0xFF00FF00), // Green
      const Color(0xFF0000FF), // Blue
      const Color(0xFF4B0082), // Indigo
      const Color(0xFF9400D3), // Violet
    ];
    final _stops = List.generate(
      _colors.length,
      (i) => i / (_colors.length - 1),
    );
    final gradient = ui.Gradient.linear(
      canvasRect.topLeft,
      canvasRect.bottomRight,
      _colors,
      _stops,
    );

    return [
      Paint()
        ..shader = gradient
        ..strokeWidth = stroke.width
        ..strokeCap = StrokeCap.round
        ..style = PaintingStyle.stroke,
    ];
  }

  /// Glow effect with shadow layers
  List<Paint> _glowPainter(Stroke stroke, Size canvasSize) {
    final canvasRect = Rect.fromLTWH(0, 0, canvasSize.width, canvasSize.height);
    final _colors = [Colors.cyan, Colors.blue, Colors.purple];
    final _stops = List.generate(
      _colors.length,
      (i) => i / (_colors.length - 1),
    );
    final gradient = ui.Gradient.linear(
      canvasRect.topLeft,
      canvasRect.bottomRight,
      _colors,
      _stops,
    );

    return [
      // Outer glow
      Paint()
        ..shader = gradient
        ..strokeWidth = stroke.width + 8
        ..strokeCap = StrokeCap.round
        ..style = PaintingStyle.stroke
        ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 8),
      // Middle glow
      Paint()
        ..shader = gradient
        ..strokeWidth = stroke.width + 4
        ..strokeCap = StrokeCap.round
        ..style = PaintingStyle.stroke
        ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 4),
      // Core
      Paint()
        ..color = Colors.white
        ..strokeWidth = stroke.width
        ..strokeCap = StrokeCap.round
        ..style = PaintingStyle.stroke,
    ];
  }

  /// Metallic effect with highlights
  List<Paint> _metallicPainter(Stroke stroke, Size canvasSize) {
    final canvasRect = Rect.fromLTWH(0, 0, canvasSize.width, canvasSize.height);

    // Gold metallic gradient
    final gradient = ui.Gradient.linear(
      Offset(canvasRect.left, canvasRect.center.dy),
      Offset(canvasRect.right, canvasRect.center.dy),
      [
        const Color(0xFFFFD700), // Gold
        const Color(0xFFFFE55C), // Light gold
        const Color(0xFFFFD700), // Gold
        const Color(0xFFB8860B), // Dark gold
        const Color(0xFFFFD700), // Gold
      ],
      [0.0, 0.25, 0.5, 0.75, 1.0],
    );

    return [
      // Shadow
      Paint()
        ..color = Colors.black45
        ..strokeWidth = stroke.width + 2
        ..strokeCap = StrokeCap.round
        ..style = PaintingStyle.stroke
        ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 2),
      // Main metallic stroke
      Paint()
        ..shader = gradient
        ..strokeWidth = stroke.width
        ..strokeCap = StrokeCap.round
        ..style = PaintingStyle.stroke,
    ];
  }

  /// Fragment shader painter (tint). Uses `example/shaders/tint.frag`.
  List<Paint> _fragmentShaderPainter(Stroke stroke, Size canvasSize) {
    if (_tintProgram == null) {
      return _defaultPainter(stroke, canvasSize);
    }

    final fs = _tintProgram!.fragmentShader();
    // Uniform layout in shader:
    // 0: u_time
    // 1..2: u_resolution.x, u_resolution.y
    fs.setFloat(0, _time);
    fs.setFloat(1, canvasSize.width);
    fs.setFloat(2, canvasSize.height);

    return [
      Paint()
        ..shader = fs
        ..strokeWidth = stroke.width
        ..strokeCap = StrokeCap.round
        ..style = PaintingStyle.stroke,
    ];
  }

  /// Multi-layer with border and shadow
  List<Paint> _multiLayerPainter(Stroke stroke, Size canvasSize) {
    final canvasRect = Rect.fromLTWH(0, 0, canvasSize.width, canvasSize.height);

    return [
      // Shadow
      paintWithOverride(
        stroke,
        strokeWidth: stroke.width + 6,
        strokeColor: Colors.black26,
      )..maskFilter = const MaskFilter.blur(BlurStyle.normal, 3),
      // Outer border
      paintWithOverride(
        stroke,
        strokeWidth: stroke.width + 4,
        strokeColor: Colors.black,
      ),
      // White highlight border
      paintWithOverride(
        stroke,
        strokeWidth: stroke.width + 2,
        strokeColor: Colors.white70,
      ),
      // Main stroke: white base color
      paintWithOverride(
        stroke,
        strokeWidth: stroke.width,
        strokeColor: Colors.white,
      ),
    ];
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Shader Effects Demo'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      body: Column(
        children: [
          // Effect selector
          Container(
            width: double.infinity,
            padding: const EdgeInsets.all(16),
            decoration: BoxDecoration(
              color: Colors.grey[100],
              border: Border(bottom: BorderSide(color: Colors.grey[300]!)),
            ),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  'Shader Effect:',
                  style: TextStyle(
                    fontSize: 14,
                    fontWeight: FontWeight.bold,
                    color: Colors.grey[700],
                  ),
                ),
                const SizedBox(height: 12),
                Wrap(
                  spacing: 8,
                  runSpacing: 8,
                  children: ShaderEffect.values.map((effect) {
                    final isSelected = _currentEffect == effect;
                    return ChoiceChip(
                      label: Column(
                        mainAxisSize: MainAxisSize.min,
                        children: [
                          Text(
                            effect.label,
                            style: TextStyle(
                              fontWeight: isSelected
                                  ? FontWeight.bold
                                  : FontWeight.normal,
                            ),
                          ),
                          if (isSelected) ...[
                            const SizedBox(height: 2),
                            Text(
                              effect.description,
                              style: TextStyle(
                                fontSize: 10,
                                color: Colors.grey[600],
                              ),
                            ),
                          ],
                        ],
                      ),
                      selected: isSelected,
                      onSelected: (selected) {
                        if (selected) {
                          setState(() => _currentEffect = effect);
                        }
                      },
                      selectedColor: Colors.deepPurple[100],
                    );
                  }).toList(),
                ),
              ],
            ),
          ),
          // Drawing canvas
          Expanded(
            child: Container(
              margin: const EdgeInsets.all(16),
              decoration: BoxDecoration(
                color: Colors.white,
                border: Border.all(color: Colors.grey[300]!, width: 2),
                borderRadius: BorderRadius.circular(8),
                boxShadow: [
                  BoxShadow(
                    color: Colors.black12,
                    blurRadius: 4,
                    offset: const Offset(0, 2),
                  ),
                ],
              ),
              child: ClipRRect(
                borderRadius: BorderRadius.circular(6),
                child: LayoutBuilder(
                  builder: (context, constraints) {
                    final canvasSize = Size(
                      constraints.maxWidth,
                      constraints.maxHeight,
                    );
                    return Draw(
                      strokes: _strokes,
                      strokeColor: Colors.black,
                      strokeWidth: 8.0,
                      backgroundColor: Colors.white,
                      onStrokeDrawn: (stroke) {
                        setState(() => _strokes = [..._strokes, stroke]);
                      },
                      strokePainter: (stroke) =>
                          _getStrokePainter(stroke, canvasSize),
                    );
                  },
                ),
              ),
            ),
          ),
          // Control buttons
          Container(
            width: double.infinity,
            padding: const EdgeInsets.all(16),
            decoration: BoxDecoration(
              color: Colors.white,
              border: Border(top: BorderSide(color: Colors.grey[300]!)),
            ),
            child: Row(
              children: [
                Expanded(
                  child: ElevatedButton.icon(
                    icon: const Icon(Icons.clear),
                    label: const Text('Clear Canvas'),
                    onPressed: _strokes.isEmpty ? null : _clear,
                    style: ElevatedButton.styleFrom(
                      backgroundColor: Colors.red[50],
                      foregroundColor: Colors.red[700],
                      padding: const EdgeInsets.symmetric(vertical: 12),
                    ),
                  ),
                ),
                const SizedBox(width: 12),
                Expanded(
                  child: ElevatedButton.icon(
                    icon: const Icon(Icons.info_outline),
                    label: const Text('About'),
                    onPressed: () => _showAboutDialog(context),
                    style: ElevatedButton.styleFrom(
                      padding: const EdgeInsets.symmetric(vertical: 12),
                    ),
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  void _showAboutDialog(BuildContext context) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('Shader Effects Demo'),
        content: SingleChildScrollView(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const Text(
                'This demo showcases various shader effects using the strokePainter feature of draw_your_image package.',
                style: TextStyle(fontSize: 14),
              ),
              const SizedBox(height: 16),
              const Text(
                'Available Effects:',
                style: TextStyle(fontWeight: FontWeight.bold),
              ),
              const SizedBox(height: 8),
              ...ShaderEffect.values.map(
                (effect) => Padding(
                  padding: const EdgeInsets.only(left: 8, bottom: 4),
                  child: Row(
                    children: [
                      const Icon(Icons.brush, size: 16),
                      const SizedBox(width: 8),
                      Expanded(
                        child: Text(
                          '${effect.label}: ${effect.description}',
                          style: const TextStyle(fontSize: 12),
                        ),
                      ),
                    ],
                  ),
                ),
              ),
            ],
          ),
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.of(context).pop(),
            child: const Text('Close'),
          ),
        ],
      ),
    );
  }
}
52
likes
160
points
1.14k
downloads

Publisher

verified publishertsuyoshichujo.com

Weekly Downloads

A Flutter package which enables users to draw with fingers in your designed UI.

Repository (GitHub)
View/report issues

Documentation

API reference

License

Apache-2.0 (license)

Dependencies

flutter

More

Packages that depend on draw_your_image