Skip to content

Instantly share code, notes, and snippets.

@pagetronic
Last active October 24, 2023 11:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pagetronic/e0cf25fe74bc99c425b8459a84e0199e to your computer and use it in GitHub Desktop.
Save pagetronic/e0cf25fe74bc99c425b8459a84e0199e to your computer and use it in GitHub Desktop.
Simple color picker for Flutter
import 'dart:math';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
class ColorPicker extends StatefulWidget {
final ValueNotifier<Color?> color;
const ColorPicker({super.key, required this.color});
@override
ColorPickerState createState() => ColorPickerState();
}
class ColorPickerState extends State<ColorPicker> {
@override
Widget build(BuildContext context) {
SlidePainter slider = SlidePainter.get(widget.color);
return LayoutBuilder(
builder: (context, constraints) {
slider.setWidth(constraints.maxWidth);
return SizedBox(
width: constraints.maxWidth,
height: 30,
child: GestureDetector(
onHorizontalDragUpdate: (DragUpdateDetails details) {
slider.setColor(details.localPosition.dx.toInt());
},
onTapDown: (TapDownDetails details) {
slider.setColor(details.localPosition.dx.toInt());
},
child: CustomPaint(size: Size(constraints.maxWidth, constraints.maxHeight), painter: slider),
),
);
},
);
}
}
class SlidePainter extends CustomPainter {
final List<Color> gradient = const [
Color.fromARGB(255, 255, 0, 0),
Color.fromARGB(255, 255, 128, 0),
Color.fromARGB(255, 255, 255, 0),
Color.fromARGB(255, 128, 255, 0),
Color.fromARGB(255, 0, 255, 0),
Color.fromARGB(255, 0, 255, 128),
Color.fromARGB(255, 0, 255, 255),
Color.fromARGB(255, 0, 128, 255),
Color.fromARGB(255, 0, 0, 255),
Color.fromARGB(255, 127, 0, 255),
Color.fromARGB(255, 255, 0, 255),
Color.fromARGB(255, 255, 0, 127),
];
double width = 1;
final ValueNotifier<Color?> color;
final Notifier repaint;
double position = -1;
final double padding = 14;
Future<void> future = Future(() {});
Future<ByteData?>? byteDataFuture;
SlidePainter(this.color, this.repaint) : super(repaint: repaint);
static SlidePainter get(final ValueNotifier<Color?> color) {
return SlidePainter(color, Notifier());
}
@override
void paint(Canvas canvas, Size size) {
Rect rect = Offset.zero & size;
canvas.clipRect(rect);
canvas.drawRRect(
RRect.fromRectAndRadius(Rect.fromPoints(Offset(0, 5 * size.height / 18), Offset(size.width, 13 * size.height / 18)), const Radius.circular(4)),
Paint()
..shader = ui.Gradient.linear(
Offset(padding - 2, 0),
Offset(size.width - padding - 2, 0),
gradient,
[for (int index = 0; index < gradient.length; index++) index / gradient.length],
),
);
canvas.drawRRect(
RRect.fromRectAndRadius(Rect.fromPoints(Offset(0, 5 * size.height / 18), Offset(size.width, 13 * size.height / 18)), const Radius.circular(4)),
Paint()
..color = Colors.grey.withOpacity(0.7)
..strokeWidth = 1
..style = PaintingStyle.stroke,
);
canvas.drawCircle(
Offset(position, size.height / 2),
12,
Paint()
..color = Colors.white.withOpacity(0.7)
..strokeWidth = 4
..style = PaintingStyle.stroke,
);
canvas.drawCircle(
Offset(position, size.height / 2),
12,
Paint()
..color = color.value != null ? color.value! : Colors.grey
..shader = ui.Gradient.radial(
Offset(position, size.height / 2),
12,
[color.value != null ? color.value! : Colors.grey, Colors.grey],
[0.75, 1],
),
);
}
@override
bool shouldRepaint(SlidePainter oldDelegate) {
return true;
}
void setColor(int dx) async {
dx = min(width.toInt() - padding.toInt() - 1, max(padding.toInt(), dx));
await future;
future = Future(() async {
byteDataFuture ??= buildByteData();
ByteData? byteData = await byteDataFuture;
if (byteData != null) {
if (dx < padding) {
position = padding;
} else if (dx >= width - padding - 1) {
position = width - padding - 1;
} else {
int position_ = dx;
position = position_.toDouble();
}
color.value = pixelColorAt(position.toInt(), byteData);
repaint.notify();
}
});
}
void setPosition() async {
Color? color = this.color.value;
if (color == null) {
position = width / 2 - padding;
repaint.notify();
return;
}
byteDataFuture ??= buildByteData();
ByteData? byteData = await byteDataFuture;
if (byteData == null) {
return;
}
for (double position = padding; position < width - padding - 1; position++) {
if (pixelColorAt(position.toInt(), byteData) == color) {
this.position = position.toDouble();
repaint.notify();
return;
}
}
List<double> diffs = [];
for (double position = padding; position < width - padding - 1; position++) {
Color color_ = pixelColorAt(position.toInt(), byteData);
int diffRed = (color.red - color_.red).abs();
int diffGreen = (color.green - color_.green).abs();
int diffBlue = (color.blue - color_.blue).abs();
diffs.add((diffRed + diffGreen + diffBlue) / 3);
}
double mini = double.infinity;
for (double diff in diffs) {
mini = min(diff, mini);
}
if (mini != double.infinity) {
position = diffs.indexOf(mini).toDouble();
repaint.notify();
}
}
void setWidth(double width) {
if (this.width != width) {
double oldWidth = this.width;
this.width = width;
byteDataFuture = null;
if (position >= 0) {
position = position * width / oldWidth;
repaint.notify();
} else {
setPosition();
}
}
}
Future<ByteData?> buildByteData() async {
ui.PictureRecorder recorder = ui.PictureRecorder();
Canvas canvas = Canvas(recorder, Rect.fromPoints(Offset.zero, Offset(width, 1)));
canvas.drawRect(Rect.fromPoints(Offset(padding, 0), Offset(width - padding, 1)), Paint()..color = Colors.black);
canvas.drawRect(
Rect.fromPoints(Offset(padding, 0), Offset(width - padding, 1)),
Paint()
..shader = ui.Gradient.linear(
Offset(padding, 0),
Offset(width - padding, 1),
gradient,
[for (int index = 0; index < gradient.length; index++) index / gradient.length],
),
);
return await (await recorder.endRecording().toImage(width.toInt(), 1)).toByteData(format: ui.ImageByteFormat.rawRgba);
}
Color pixelColorAt(int x, ByteData? byteData) {
if (byteData == null || x < 0 || x >= width) {
return Colors.transparent;
}
int byteOffset = 4 * (x);
int rgbaColor = byteData.getUint32(byteOffset);
int a = rgbaColor & 0xFF;
int rgb = rgbaColor >> 8;
return Color(rgb + (a << 24));
}
}
class Notifier extends ChangeNotifier {
void notify() {
notifyListeners();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment