fast_thumbhash 1.1.1 copy "fast_thumbhash: ^1.1.1" to clipboard
fast_thumbhash: ^1.1.1 copied to clipboard

Ultra-fast ThumbHash encoder/decoder for Dart and Flutter. A very compact representation of image placeholders with alpha support and natural loading transitions.

example/example.dart

// ignore_for_file: avoid_print

import 'dart:convert';
import 'dart:typed_data';

import 'package:fast_thumbhash/fast_thumbhash.dart';
import 'package:flutter/material.dart';

/// Example usage of the fast_thumbhash package.
///
/// This file demonstrates the main features and use cases.

void main() {
  runApp(const ThumbHashExampleApp());
}

/// Example Flutter app demonstrating ThumbHash usage.
class ThumbHashExampleApp extends StatelessWidget {
  const ThumbHashExampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'fast_thumbhash Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: const ThumbHashExamplePage(),
    );
  }
}

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

  // Sample ThumbHash strings (base64 encoded)
  static const sampleHashes = [
    '3OcRJYB4d3h/iIeHeEh3eIhw+j3A', // Landscape, no alpha
    '3OiFBQAziIWCjHn3aGpwZ/moB8iHeHR6Zw==', // Square, with alpha
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('fast_thumbhash Example'),
      ),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          // Basic usage examples
          _buildSection(
            context,
            'Basic Usage',
            Column(
              children: sampleHashes.map((hash) {
                return _buildBasicExample(context, hash);
              }).toList(),
            ),
          ),

          const SizedBox(height: 24),

          // Natural loading with transitions
          _buildSection(
            context,
            'Natural Loading (ThumbHashPlaceholder)',
            _buildNaturalLoadingExample(context),
          ),

          const SizedBox(height: 24),

          // Custom builder example
          _buildSection(
            context,
            'Custom Builder (ThumbHashImageBuilder)',
            _buildBuilderExample(context),
          ),

          const SizedBox(height: 24),

          // Image loading pattern (manual)
          _buildSection(
            context,
            'Manual Image Loading Pattern',
            _buildImageLoadingExample(context),
          ),

          const SizedBox(height: 24),

          // API demonstration
          _buildSection(
            context,
            'API Features',
            _buildApiExample(context),
          ),
        ],
      ),
    );
  }

  Widget _buildSection(BuildContext context, String title, Widget child) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(title, style: Theme.of(context).textTheme.titleLarge),
            const SizedBox(height: 16),
            child,
          ],
        ),
      ),
    );
  }

  Widget _buildBasicExample(BuildContext context, String base64Hash) {
    final thumbHash = ThumbHash.fromBase64(base64Hash);

    return Padding(
      padding: const EdgeInsets.only(bottom: 16),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // ThumbHash image
          ClipRRect(
            borderRadius: BorderRadius.circular(8),
            child: SizedBox(
              width: 100,
              height: 100 / thumbHash.toAspectRatio(),
              child: Image(
                image: thumbHash.toImage(),
                fit: BoxFit.cover,
              ),
            ),
          ),
          const SizedBox(width: 16),
          // Info
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  'Hash: ${base64Hash.substring(0, 20)}...',
                  style: Theme.of(context).textTheme.bodySmall,
                ),
                const SizedBox(height: 4),
                Text('Aspect ratio: ${thumbHash.toAspectRatio().toStringAsFixed(2)}'),
                Text('Has alpha: ${thumbHash.hasAlpha}'),
                Text('Is landscape: ${thumbHash.isLandscape}'),
                const SizedBox(height: 8),
                // Average color swatch
                Row(
                  children: [
                    const Text('Average color: '),
                    Container(
                      width: 24,
                      height: 24,
                      decoration: BoxDecoration(
                        color: thumbHash.toAverageColor(),
                        borderRadius: BorderRadius.circular(4),
                        border: Border.all(color: Colors.grey),
                      ),
                    ),
                  ],
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildNaturalLoadingExample(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Text(
          'Automatic smooth transitions from placeholder to loaded image:',
        ),
        const SizedBox(height: 16),

        // Fade transition
        _buildTransitionDemo(
          context,
          'Fade (Default)',
          ThumbHash.fromBase64(sampleHashes[0]),
          TransitionConfig.fast,
        ),
        const SizedBox(height: 12),

        // Blur to sharp
        _buildTransitionDemo(
          context,
          'Blur to Sharp',
          ThumbHash.fromBase64(sampleHashes[0]),
          TransitionConfig.blur,
        ),
        const SizedBox(height: 12),

        // Scale up
        _buildTransitionDemo(
          context,
          'Scale Up',
          ThumbHash.fromBase64(sampleHashes[1]),
          TransitionConfig.scale,
        ),
        const SizedBox(height: 12),

        // Smooth
        _buildTransitionDemo(
          context,
          'Smooth (400ms)',
          ThumbHash.fromBase64(sampleHashes[1]),
          TransitionConfig.smooth,
        ),
      ],
    );
  }

  Widget _buildTransitionDemo(
    BuildContext context,
    String label,
    ThumbHash thumbHash,
    TransitionConfig config,
  ) {
    // Use a random image URL with cache-busting to simulate loading
    final imageUrl =
        'https://picsum.photos/seed/${label.hashCode}/300/200';

    return Row(
      children: [
        Expanded(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(label, style: const TextStyle(fontWeight: FontWeight.bold)),
              Text(
                '${config.type.name}, ${config.duration.inMilliseconds}ms',
                style: Theme.of(context).textTheme.bodySmall,
              ),
            ],
          ),
        ),
        ClipRRect(
          borderRadius: BorderRadius.circular(8),
          child: SizedBox(
            width: 120,
            height: 80,
            child: ThumbHashPlaceholder(
              thumbHash: thumbHash,
              image: NetworkImage(imageUrl),
              transition: config,
              fit: BoxFit.cover,
            ),
          ),
        ),
      ],
    );
  }

  Widget _buildBuilderExample(BuildContext context) {
    final thumbHash = ThumbHash.fromBase64(sampleHashes[0]);
    const imageUrl = 'https://picsum.photos/seed/builder/400/300';

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Text(
          'Full control with ThumbHashImageBuilder:',
        ),
        const SizedBox(height: 12),
        ClipRRect(
          borderRadius: BorderRadius.circular(8),
          child: SizedBox(
            height: 200,
            width: double.infinity,
            child: ThumbHashImageBuilder(
              thumbHash: thumbHash,
              image: const NetworkImage(imageUrl),
              transition: TransitionConfig.smooth,
              onLoaded: () {
                // Called when image loads
              },
              onError: (error, stackTrace) {
                // Handle error
              },
              builder: (context, state) {
                return Stack(
                  fit: StackFit.expand,
                  children: [
                    // Background with average color
                    Container(color: thumbHash.toAverageColor()),

                    // Placeholder image
                    Opacity(
                      opacity: 1.0 - state.progress * 0.5,
                      child: Image(
                        image: state.placeholderImage,
                        fit: BoxFit.cover,
                      ),
                    ),

                    // Loaded image fades in
                    if (state.isLoaded && state.loadedImageInfo != null)
                      Opacity(
                        opacity: state.progress,
                        child: RawImage(
                          image: state.loadedImageInfo!.image,
                          fit: BoxFit.cover,
                        ),
                      ),

                    // Loading indicator
                    if (!state.isLoaded && !state.hasError)
                      Center(
                        child: Container(
                          padding: const EdgeInsets.all(12),
                          decoration: BoxDecoration(
                            color: Colors.black38,
                            borderRadius: BorderRadius.circular(8),
                          ),
                          child: const CircularProgressIndicator(
                            color: Colors.white,
                            strokeWidth: 2,
                          ),
                        ),
                      ),

                    // Error state
                    if (state.hasError)
                      const Center(
                        child: Icon(Icons.error, color: Colors.red, size: 48),
                      ),

                    // Progress indicator
                    Positioned(
                      bottom: 8,
                      right: 8,
                      child: Container(
                        padding: const EdgeInsets.symmetric(
                          horizontal: 8,
                          vertical: 4,
                        ),
                        decoration: BoxDecoration(
                          color: Colors.black54,
                          borderRadius: BorderRadius.circular(4),
                        ),
                        child: Text(
                          state.isLoaded
                              ? 'Loaded (${(state.progress * 100).toInt()}%)'
                              : 'Loading...',
                          style: const TextStyle(
                            color: Colors.white,
                            fontSize: 12,
                          ),
                        ),
                      ),
                    ),
                  ],
                );
              },
            ),
          ),
        ),
      ],
    );
  }

  Widget _buildImageLoadingExample(BuildContext context) {
    final thumbHash = ThumbHash.fromBase64(sampleHashes[0]);
    const imageUrl = 'https://picsum.photos/seed/manual/400/300';

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Text(
          'Manual implementation with Stack (for reference):',
        ),
        const SizedBox(height: 12),
        ClipRRect(
          borderRadius: BorderRadius.circular(8),
          child: AspectRatio(
            aspectRatio: thumbHash.toAspectRatio(),
            child: Stack(
              fit: StackFit.expand,
              children: [
                // ThumbHash placeholder
                Image(image: thumbHash.toImage(), fit: BoxFit.cover),
                // Network image
                Image.network(
                  imageUrl,
                  fit: BoxFit.cover,
                  loadingBuilder: (context, child, progress) {
                    if (progress == null) {
                      // Loading complete
                      return child;
                    }
                    return Center(
                      child: CircularProgressIndicator(
                        value: progress.expectedTotalBytes != null
                            ? progress.cumulativeBytesLoaded /
                                progress.expectedTotalBytes!
                            : null,
                      ),
                    );
                  },
                  errorBuilder: (context, error, stack) {
                    return const Center(child: Icon(Icons.error));
                  },
                ),
              ],
            ),
          ),
        ),
      ],
    );
  }

  Widget _buildApiExample(BuildContext context) {
    final hash = ThumbHash.fromBase64(sampleHashes[0]);
    final image = hash.toRGBA();
    final color = hash.toAverageRGBA();

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        _buildCodeExample(
          'ThumbHash.fromBase64()',
          "final hash = ThumbHash.fromBase64('3OcRJYB4...');",
        ),
        _buildCodeExample(
          'toRGBA()',
          'final image = hash.toRGBA();\n'
              '// width: ${image.width}, height: ${image.height}\n'
              '// pixels: ${image.pixelCount}',
        ),
        _buildCodeExample(
          'toAverageRGBA()',
          'final color = hash.toAverageRGBA();\n'
              '// r: ${color.r.toStringAsFixed(3)}, g: ${color.g.toStringAsFixed(3)}\n'
              '// b: ${color.b.toStringAsFixed(3)}, a: ${color.a.toStringAsFixed(3)}',
        ),
        _buildCodeExample(
          'toAspectRatio()',
          'final ratio = hash.toAspectRatio();\n'
              '// ratio: ${hash.toAspectRatio().toStringAsFixed(2)}',
        ),
      ],
    );
  }

  Widget _buildCodeExample(String title, String code) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 12),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(title, style: const TextStyle(fontWeight: FontWeight.bold)),
          const SizedBox(height: 4),
          Container(
            width: double.infinity,
            padding: const EdgeInsets.all(8),
            decoration: BoxDecoration(
              color: Colors.grey.shade100,
              borderRadius: BorderRadius.circular(4),
            ),
            child: Text(
              code,
              style: const TextStyle(
                fontFamily: 'monospace',
                fontSize: 12,
              ),
            ),
          ),
        ],
      ),
    );
  }
}

// =============================================================================
// Pure Dart Examples (no Flutter required)
// =============================================================================

/// Demonstrates pure Dart usage without Flutter.
void pureDartExample() {
  print('=== Pure Dart ThumbHash Example ===\n');

  // Decode a ThumbHash
  const base64Hash = '3OcRJYB4d3h/iIeHeEh3eIhw+j3A';
  final hashBytes = Uint8List.fromList(base64.decode(base64Hash));

  // Get image dimensions and pixels
  final image = thumbHashToRGBA(hashBytes);
  print('Decoded image: ${image.width}x${image.height}');
  print('Total pixels: ${image.pixelCount}');
  print('Buffer size: ${image.rgba.length} bytes\n');

  // Get average color
  final color = thumbHashToAverageRGBA(hashBytes);
  print('Average color:');
  print('  R: ${(color.r * 255).round()}');
  print('  G: ${(color.g * 255).round()}');
  print('  B: ${(color.b * 255).round()}');
  print('  A: ${(color.a * 255).round()}\n');

  // Get aspect ratio
  final ratio = thumbHashToApproximateAspectRatio(hashBytes);
  print('Aspect ratio: ${ratio.toStringAsFixed(2)}');
  print('Orientation: ${ratio > 1 ? "landscape" : "portrait"}\n');

  // Encode to PNG
  final png = thumbHashImageToPng(image);
  print('PNG size: ${png.length} bytes');
  print('PNG signature valid: ${png[0] == 0x89 && png[1] == 0x50}');
}

/// Demonstrates encoding an image to ThumbHash.
void encodingExample() {
  print('\n=== ThumbHash Encoding Example ===\n');

  // Create a simple 32x32 gradient image
  const w = 32, h = 32;
  final rgba = Uint8List(w * h * 4);

  for (var y = 0; y < h; y++) {
    for (var x = 0; x < w; x++) {
      final i = (y * w + x) * 4;
      rgba[i] = (x * 255 ~/ w); // R: horizontal gradient
      rgba[i + 1] = (y * 255 ~/ h); // G: vertical gradient
      rgba[i + 2] = 128; // B: constant
      rgba[i + 3] = 255; // A: fully opaque
    }
  }

  // Encode to ThumbHash
  final hash = rgbaToThumbHash(w, h, rgba);
  final base64Hash = base64.encode(hash);

  print('Input image: ${w}x$h');
  print('ThumbHash size: ${hash.length} bytes');
  print('Base64: $base64Hash');

  // Verify by decoding
  final decoded = thumbHashToRGBA(hash);
  print('Decoded size: ${decoded.width}x${decoded.height}');
}
2
likes
0
points
610
downloads

Publisher

unverified uploader

Weekly Downloads

Ultra-fast ThumbHash encoder/decoder for Dart and Flutter. A very compact representation of image placeholders with alpha support and natural loading transitions.

Homepage
Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter

More

Packages that depend on fast_thumbhash