import 'dart:typed_data';
import 'dart:ui' as ui;
import 'dart:math' as math;
import 'dart:async';

import 'package:flutter/animation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
import 'package:vector_math/vector_math.dart';

const OCTAVES = 6;

double fract(double x) {
  return x - x.floor();
}

double random(Vector2 st) {
  return fract(math.sin(dot2(st.xy, Vector2(12.9898, 78.233))) * 43758.5453123);
}

double noise(Vector2 st) {
  Vector2 i = Vector2(st.x.floor().toDouble(), st.y.floor().toDouble());
  Vector2 f = Vector2(fract(st.x), fract(st.y));

  double a = random(i + Vector2(0.0, 0.0));
  double b = random(i + Vector2(1.0, 0.0));
  double c = random(i + Vector2(0.0, 1.0));
  double d = random(i + Vector2(1.0, 1.0));

  Vector2 u = Vector2(
    smoothStep(0.0, 1.0, f.x),
    smoothStep(0.0, 1.0, f.y),
  );

  double result =
      mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;

  return result;
}

double fbm(Vector2 st) {
  double value = 0.0;
  double amplitude = .5;

  for (int i = 0; i < OCTAVES; i++) {
    value += amplitude * noise(st);
    st *= 2.0;
    amplitude *= 0.5;
  }
  return value;
}

class NonStopVSync implements TickerProvider {
  const NonStopVSync();
  @override
  Ticker createTicker(TickerCallback onTick) => Ticker(onTick);
}

const alphaOffset = 24;
const redOffset = 16;
const greenOffset = 8;
const blueOffset = 0;

const kImageDimension = 60;

int makeColor(double time, int x, int y) {
  // main function of GLSL.
  int red = 0;
  int green = 0;
  int blue = 0;
  int alpha = 255;
  int resultColor = 0;
  Vector2 p = Vector2(
    (x.toDouble() * 2 - kImageDimension) / kImageDimension,
    (y.toDouble() * 2 - kImageDimension) / kImageDimension,
  );

  // color processing here
  double primary = fbm(p * 2.0);
  Vector2 secondary = Vector2(
    p.x + primary + time,
    p.y + primary + time,
  );
  red = (fbm(secondary) * 255).toInt();
  green = red;
  blue = red;

  // convert 8bit integers to 32bit integers
  resultColor += (alpha << alphaOffset);
  resultColor += (red << redOffset);
  resultColor += (green << greenOffset);
  resultColor += (blue << blueOffset);

  return resultColor;
}

Future<ui.Image> makeImage({double time = 0}) {
  final c = Completer<ui.Image>();
  final pixels = Int32List(kImageDimension * kImageDimension);
  int x = 0;
  int y = 0;
  for (int i = 0; i < pixels.length; i++) {
    y = (i / kImageDimension).floor();
    x = i % kImageDimension;
    pixels[i] = makeColor(time, x, y);
  }
  ui.decodeImageFromPixels(
    pixels.buffer.asUint8List(),
    kImageDimension,
    kImageDimension,
    ui.PixelFormat.rgba8888,
    c.complete,
  );
  return c.future;
}

void main() async {
  const imageSize = 60.0;

  final RenderImage image = RenderImage(
    width: imageSize,
    height: imageSize,
    image: await makeImage(),
  );

  final RenderBox imageWrap = RenderConstrainedBox(
    additionalConstraints:
        const BoxConstraints.tightFor(width: imageSize, height: imageSize),
    child: image,
  );
  final RenderBox root = RenderPositionedBox(
    alignment: Alignment.center,
    child: imageWrap,
  );
  RenderingFlutterBinding(root: root);

  final AnimationController animation = AnimationController(
    duration: const Duration(milliseconds: 800),
    vsync: const NonStopVSync(),
  )..repeat();

  double time = 0.0;

  animation.addListener(() async {
    image.image = await makeImage(time: time);
    time += 0.01;
  });
}
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.