-
-
Save roipeker/9315aa25301f5c0362caaebd15876c2f to your computer and use it in GitHub Desktop.
////////////////////////////// | |
// | |
// 2019, roipeker.com | |
// screencast - demo simple image: | |
// https://youtu.be/EJyRH4_pY8I | |
// | |
// screencast - demo snapshot: | |
// https://youtu.be/-LxPcL7T61E | |
// | |
////////////////////////////// | |
import 'dart:async'; | |
import 'dart:typed_data'; | |
import 'dart:ui' as ui; | |
import 'package:flutter/material.dart'; | |
import 'package:flutter/rendering.dart'; | |
import 'package:image/image.dart' as img; | |
import 'package:flutter/services.dart' show rootBundle; | |
class ColorPickerWidget extends StatefulWidget { | |
@override | |
_ColorPickerWidgetState createState() => _ColorPickerWidgetState(); | |
} | |
class _ColorPickerWidgetState extends State<ColorPickerWidget> { | |
String imagePath = 'assets/images/santorini.jpg'; | |
GlobalKey imageKey = GlobalKey(); | |
GlobalKey paintKey = GlobalKey(); | |
// CHANGE THIS FLAG TO TEST BASIC IMAGE, AND SNAPSHOT. | |
bool useSnapshot = true; | |
// based on useSnapshot=true ? paintKey : imageKey ; | |
// this key is used in this example to keep the code shorter. | |
GlobalKey currentKey; | |
final StreamController<Color> _stateController = StreamController<Color>(); | |
img.Image photo; | |
@override | |
void initState() { | |
currentKey = useSnapshot ? paintKey : imageKey; | |
super.initState(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
final String title = useSnapshot ? "snapshot" : "basic"; | |
return Scaffold( | |
appBar: AppBar(title: Text("Color picker $title")), | |
body: StreamBuilder( | |
initialData: Colors.green[500], | |
stream: _stateController.stream, | |
builder: (buildContext, snapshot) { | |
Color selectedColor = snapshot.data ?? Colors.green; | |
return Stack( | |
children: <Widget>[ | |
RepaintBoundary( | |
key: paintKey, | |
child: GestureDetector( | |
onPanDown: (details) { | |
searchPixel(details.globalPosition); | |
}, | |
onPanUpdate: (details) { | |
searchPixel(details.globalPosition); | |
}, | |
child: Center( | |
child: Image.asset( | |
imagePath, | |
key: imageKey, | |
//color: Colors.red, | |
//colorBlendMode: BlendMode.hue, | |
//alignment: Alignment.bottomRight, | |
fit: BoxFit.none, | |
//scale: .8, | |
), | |
), | |
), | |
), | |
Container( | |
margin: EdgeInsets.all(70), | |
width: 50, | |
height: 50, | |
decoration: BoxDecoration( | |
shape: BoxShape.circle, | |
color: selectedColor, | |
border: Border.all(width: 2.0, color: Colors.white), | |
boxShadow: [ | |
BoxShadow( | |
color: Colors.black12, | |
blurRadius: 4, | |
offset: Offset(0, 2)) | |
]), | |
), | |
Positioned( | |
child: Text('${selectedColor}', | |
style: TextStyle( | |
color: Colors.white, | |
backgroundColor: Colors.black54)), | |
left: 114, | |
top: 95, | |
), | |
], | |
); | |
}), | |
); | |
} | |
void searchPixel(Offset globalPosition) async { | |
if (photo == null) { | |
await (useSnapshot ? loadSnapshotBytes() : loadImageBundleBytes()); | |
} | |
_calculatePixel(globalPosition); | |
} | |
void _calculatePixel(Offset globalPosition) { | |
RenderBox box = currentKey.currentContext.findRenderObject(); | |
Offset localPosition = box.globalToLocal(globalPosition); | |
double px = localPosition.dx; | |
double py = localPosition.dy; | |
if (!useSnapshot) { | |
double widgetScale = box.size.width / photo.width; | |
print(py); | |
px = (px / widgetScale); | |
py = (py / widgetScale); | |
} | |
int pixel32 = photo.getPixelSafe(px.toInt(), py.toInt()); | |
int hex = abgrToArgb(pixel32); | |
_stateController.add(Color(hex)); | |
} | |
Future<void> loadImageBundleBytes() async { | |
ByteData imageBytes = await rootBundle.load(imagePath); | |
setImageBytes(imageBytes); | |
} | |
Future<void> loadSnapshotBytes() async { | |
RenderRepaintBoundary boxPaint = paintKey.currentContext.findRenderObject(); | |
ui.Image capture = await boxPaint.toImage(); | |
ByteData imageBytes = | |
await capture.toByteData(format: ui.ImageByteFormat.png); | |
setImageBytes(imageBytes); | |
capture.dispose(); | |
} | |
void setImageBytes(ByteData imageBytes) { | |
List<int> values = imageBytes.buffer.asUint8List(); | |
photo = null; | |
photo = img.decodeImage(values); | |
} | |
} | |
// image lib uses uses KML color format, convert #AABBGGRR to regular #AARRGGBB | |
int abgrToArgb(int argbColor) { | |
int r = (argbColor >> 16) & 0xFF; | |
int b = argbColor & 0xFF; | |
return (argbColor & 0xFF00FF00) | (b << 16) | r; | |
} |
Here’s a quick sample of the approach i commented before:
https://gist.github.com/roipeker/98fadef42c470227f9e72ab0ea18ec74
Live demo: https://roi-gradient-picker.surge.sh/
https://i.stack.imgur.com/miTWO.jpg*I Sorry can you help me? want to take a picture like this analysis paper, all the colors change. I want when I shoot the picture, I can see the degrees of each RGB color and compare them with my degrees, and according to each degree I look to diagnose a specific disease. The 10 colors that are in any picture I take are like this picture
thanks.
@Ghanam330 i doubt u can reliably do any diagnosis like that. The camera, environment and lighting conditions will change, even the surface where u show the color samples. I think some image analysis AI is more appropriate for the tasks to at least evaluate the false positives u might encounter.
@roipeker
I apologize for disturbing you.
I will go to something from AI, but what is the idea that I can link to, that I send the image, for example, to the API that does the analysis and then returns the data to me again.
And is there a way, for example, that I put it in the web view of the application?
Thanks for your reply, and sorry if I bothered you
@Ghanam330 to be honest, its beyond my knowledge. If you rely on some server implementation, i guess you will just post the image to some service and get some data results you can use. In any way, at Flutter level, bitmap manipulation is a no-go for now. No apis exposed natively from skia, and u have to rely on 3rd psrty packages like inage to do some pixel level manipulation, which is VERY slow. I think the ifea in your case is to find the boundries of the sample colors and detect a pattern, like the qr code does, so u can get the bounds of pixels relevant to color analisis. Then maybe apply some blur filter and level correction to normalize the noise, and maybe reduce the color palette of the images (like gif does)… at least that way u might have an easier way to compare colors. Reducing the image to a very small size can also average the dominant color. But sadly, I have no input for you beyond those ideas.
I am trying to run this code but get these errors as given below. I have also checked on your youtube video on this and am not sure why these errors are throwing up. Not sure if its something to do with the Null check..
Line 47: Color selectedColor = snapshot.data ?? Colors.green;
Error: A value of type 'Object' can't be assigned to a variable of type 'Color'.
Try changing the type of the variable, or casting the right-hand type to 'Color'.dartinvalid_assignment
Line 109: RenderBox box = currentKey.currentContext.findRenderObject();
Error: A value of type 'RenderObject?' can't be assigned to a variable of type 'RenderBox'.
Try changing the type of the variable, or casting the right-hand type to 'RenderBox'.dart
Line 134: RenderRepaintBoundary boxPaint = paintKey.currentContext.findRenderObject();
Error: A value of type 'RenderObject?' can't be assigned to a variable of type 'RenderRepaintBoundary'.
Try changing the type of the variable, or casting the right-hand type to 'RenderRepaintBoundary'.
Updated Code Without error.
import 'dart:async';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:image/image.dart' as img;
import 'package:kaizen/constants/asset_paths.dart';
class ColorPickerWidget extends StatefulWidget {
@OverRide
_ColorPickerWidgetState createState() => _ColorPickerWidgetState();
}
class _ColorPickerWidgetState extends State {
String imagePath ="assests/images/your_image.png";
GlobalKey imageKey = GlobalKey();
GlobalKey paintKey = GlobalKey();
// CHANGE THIS FLAG TO TEST BASIC IMAGE, AND SNAPSHOT.
bool useSnapshot = true;
// based on useSnapshot=true ? paintKey : imageKey ;
// this key is used in this example to keep the code shorter.
late GlobalKey currentKey;
final StreamController _stateController = StreamController();
img.Image? photo;
@OverRide
void initState() {
currentKey = useSnapshot ? paintKey : imageKey;
super.initState();
}
@OverRide
Widget build(BuildContext context) {
final String title = useSnapshot ? "snapshot" : "basic";
return Scaffold(
appBar: AppBar(title: Text("Color picker $title")),
body: StreamBuilder(
initialData: Colors.green[500],
stream: _stateController.stream,
builder: (buildContext, snapshot) {
Color selectedColor = (snapshot.data ?? Colors.green) as Color;
return Stack(
children: [
RepaintBoundary(
key: paintKey,
child: GestureDetector(
onPanDown: (details) {
searchPixel(details.globalPosition);
},
onPanUpdate: (details) {
searchPixel(details.globalPosition);
},
child: Image.asset(
imagePath,
key: imageKey,
height: 500,
width: 500,
//color: Colors.red,
//colorBlendMode: BlendMode.hue,
//alignment: Alignment.bottomRight,
fit: BoxFit.cover,
//scale: .8,
),
),
),
Container(
margin: EdgeInsets.all(70),
width: 50,
height: 50,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: selectedColor,
border: Border.all(width: 2.0, color: Colors.white),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 4,
offset: Offset(0, 2))
]),
),
Positioned(
child: Text('${selectedColor}',
style: TextStyle(
color: Colors.white,
backgroundColor: Colors.black54)),
left: 114,
top: 95,
),
],
);
}),
);
}
void searchPixel(Offset globalPosition) async {
if (photo == null) {
await (useSnapshot ? loadSnapshotBytes() : loadImageBundleBytes());
}
_calculatePixel(globalPosition);
}
void _calculatePixel(Offset globalPosition) {
RenderBox box = currentKey.currentContext?.findRenderObject() as RenderBox;
Offset localPosition = box.globalToLocal(globalPosition);
double px = localPosition.dx;
double py = localPosition.dy;
if (!useSnapshot) {
double widgetScale = box.size.width / photo!.width;
print(py);
px = (px / widgetScale);
py = (py / widgetScale);
}
int pixel32 = photo!.getPixelSafe(px.toInt(), py.toInt());
int hex = abgrToArgb(pixel32);
_stateController.add(Color(hex));
}
Future loadImageBundleBytes() async {
ByteData imageBytes = await rootBundle.load(imagePath);
setImageBytes(imageBytes);
}
Future loadSnapshotBytes() async {
RenderRepaintBoundary boxPaint =
paintKey.currentContext?.findRenderObject() as RenderRepaintBoundary;
ui.Image capture = await boxPaint.toImage();
ByteData? imageBytes =
await capture.toByteData(format: ui.ImageByteFormat.png);
setImageBytes(imageBytes!);
capture.dispose();
}
void setImageBytes(ByteData imageBytes) {
List values = imageBytes.buffer.asUint8List();
photo = img.decodeImage(values)!;
}
}
// image lib uses uses KML color format, convert #AABBGGRR to regular #AARRGGBB
int abgrToArgb(int argbColor) {
int r = (argbColor >> 16) & 0xFF;
int b = argbColor & 0xFF;
return (argbColor & 0xFF00FF00) | (b << 16) | r;
}
Thanks.
Will try and revert.
`//////////////////////////////
//
// 2019, roipeker.com
// screencast - demo simple image:
// https://youtu.be/EJyRH4_pY8I
//
// screencast - demo snapshot:
// https://youtu.be/-LxPcL7T61E
//
//////////////////////////////
import 'dart:async';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:image/image.dart' as img;
import 'package:flutter/services.dart' show rootBundle;
void main() => runApp(const MaterialApp(home: MyApp()));
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@OverRide
State createState() => _MyAppState();
}
class _MyAppState extends State {
String imagePath = 'assets/12.jpg';
GlobalKey imageKey = GlobalKey();
GlobalKey paintKey = GlobalKey();
// CHANGE THIS FLAG TO TEST BASIC IMAGE, AND SNAPSHOT.
bool useSnapshot = true;
// based on useSnapshot=true ? paintKey : imageKey ;
// this key is used in this example to keep the code shorter.
late GlobalKey currentKey;
final StreamController _stateController = StreamController();
//late img.Image photo ;
img.Image? photo;
@OverRide
void initState() {
currentKey = useSnapshot ? paintKey : imageKey;
super.initState();
}
@OverRide
Widget build(BuildContext context) {
final String title = useSnapshot ? "snapshot" : "basic";
return SafeArea(
child: Scaffold(
appBar: AppBar(title: Text("Color picker $title")),
body: StreamBuilder(
initialData: Colors.green[500],
stream: _stateController.stream,
builder: (buildContext, snapshot) {
Color selectedColor = snapshot.data as Color ?? Colors.green;
return Stack(
children: [
RepaintBoundary(
key: paintKey,
child: GestureDetector(
onPanDown: (details) {
searchPixel(details.globalPosition);
},
onPanUpdate: (details) {
searchPixel(details.globalPosition);
},
child: Center(
child: Image.asset(
imagePath,
key: imageKey,
//color: Colors.red,
//colorBlendMode: BlendMode.hue,
//alignment: Alignment.bottomRight,
fit: BoxFit.contain,
//scale: .8,
),
),
),
),
Container(
margin: const EdgeInsets.all(70),
width: 50,
height: 50,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: selectedColor!,
border: Border.all(width: 2.0, color: Colors.white),
boxShadow: [
const BoxShadow(
color: Colors.black12,
blurRadius: 4,
offset: Offset(0, 2))
]),
),
Positioned(
child: Text('${selectedColor}',
style: const TextStyle(
color: Colors.white,
backgroundColor: Colors.black54)),
left: 114,
top: 95,
),
],
);
}),
),
);
}
void searchPixel(Offset globalPosition) async {
if (photo == null) {
await (useSnapshot ? loadSnapshotBytes() : loadImageBundleBytes());
}
_calculatePixel(globalPosition);
}
void _calculatePixel(Offset globalPosition) {
RenderBox box = currentKey.currentContext!.findRenderObject() as RenderBox;
Offset localPosition = box.globalToLocal(globalPosition);
double px = localPosition.dx;
double py = localPosition.dy;
if (!useSnapshot) {
double widgetScale = box.size.width / photo!.width;
print(py);
px = (px / widgetScale);
py = (py / widgetScale);
}
int pixel32 = photo!.getPixelSafe(px.toInt(), py.toInt());
int hex = abgrToArgb(pixel32);
_stateController.add(Color(hex));
}
Future loadImageBundleBytes() async {
ByteData imageBytes = await rootBundle.load(imagePath);
setImageBytes(imageBytes);
}
Future loadSnapshotBytes() async {
RenderRepaintBoundary boxPaint =
paintKey.currentContext!.findRenderObject() as RenderRepaintBoundary;
//RenderObject? boxPaint = paintKey.currentContext.findRenderObject();
ui.Image capture = await boxPaint.toImage();
ByteData? imageBytes =
await capture.toByteData(format: ui.ImageByteFormat.png);
setImageBytes(imageBytes!);
capture.dispose();
}
void setImageBytes(ByteData imageBytes) {
List values = imageBytes.buffer.asUint8List();
photo;
photo = img.decodeImage(values)!;
}
}
// image lib uses uses KML color format, convert #AABBGGRR to regular #AARRGGBB
int abgrToArgb(int argbColor) {
int r = (argbColor >> 16) & 0xFF;
int b = argbColor & 0xFF;
return (argbColor & 0xFF00FF00) | (b << 16) | r;
}
`
`///////////////////////////////////// 2019, roipeker.com // screencast - demo hình ảnh Đơn giản: // https://youtu.be/EJyRH4_pY8I // // ghi màn hình - ảnh chụp demo: // https://youtu.be/-LxPcL7T61E // ///////// / //////////////////
nhập 'phi tiêu: không đồng bộ'; nhập 'phi tiêu:typed_data'; nhập 'phi tiêu: ui' dưới dạng ui; nhập 'phi tiêu: toán học';
nhập 'gói: rung/liệu.dart'; nhập 'gói: rung/rendering.dart'; nhập 'gói: image/image.dart' dưới img; nhập 'gói: rung/services.dart' hiển thị rootBundle;
void main() => runApp(const MaterialApp(home: MyApp()));
Lớp MyApp mở rộng StatefulWidget { const MyApp({Key? key}): super(key: key);
@OverRide Trạng thái createState() => _MyAppState(); }
Lớp _MyAppState mở rộng Trạng thái { string imagePath = 'nội dung/12.jpg'; GlobalKey imageKey = GlobalKey(); GlobalKey paintKey = GlobalKey();
// THAY ĐỔI CỜ NÀY ĐỂ KIỂM TRA ẢNH CƠ BẢN VÀ ẢNH CHỤP. bool useSnapshot = true;
// dựa trên useSnapshot=true ? paintKey : imageKey ; // phím này đã được sử dụng trong ví dụ này để giữ mã ngắn hơn. GlobalKey currentKey lần lượt;
StreamController cuối cùng _stateController = StreamController(); //muộn img.Ảnh ; img.Hình ảnh? tấm hình;
@OverRide void initState() { currentKey = useSnapshot ? paintKey : imageKey; super.initState(); }
@OverRide Bản dựng tiện ích con (Bối cảnh BuildContext) { tiêu đề Chuỗi cuối cùng = useSnapshot? "ảnh chụp nhanh" : "cơ bản"; return SafeArea( con: Scaffold( appBar: AppBar(title: Text("Bộ chọn màu $title")), body: StreamBuilder( initData : Colors.green[500], stream: _stateController.stream, builder: (buildContext, snapshot ) { Color selectColor = snapshot.data as Color ?? Colors.green; return Stack( children: [ RepaintBoundary( key: paintKey, child: GestureDetector( onPanDown: (details) { searchPixel(details.globalPosition); }, onPanUpdate: ( chi tiết) ) { searchPixel(details.globalPosition); }, con: Center( con: Image.asset( imagePath, key: imageKey, //color: Colors.red, //colorBlendMode: BlendMode.hue, //alignment: Alignment.bottomRight, fit: BoxFit.contain, // scale: .8, ), ), ), ), Khu vực chứa( lề: const EdgeInsets.all(70), chiều rộng: 50, chiều cao: 50, trang trí: BoxDecoration( format: BoxShape.circle, color : đã chọnColor!, đường viền: Đường viền.tất cả(chiều rộng: 2.0, màu:Colors.white), boxShadow: [ const BoxShadow( color: Colors.black12, blurRadius: 4, offset: Offset(0, 2)) ]), ), Đã định vị( con: Văn bản('${selectedColor}', type: const TextStyle( color: Colors.white , backgroundColor: Colors.black54)), left: 114, top: 95, ), ], ); }), ), ); }
void searchPixel(Offset globalPosition) async { if (photo == null) { đang chờ (useSnapshot ? loadSnapshotBytes() : loadImageBundleBytes()); } _calculatePixel(globalPosition); }
void _calculatePixel(Offset globalPosition) { RenderBox box = currentKey.currentContext!.findRenderObject() làm RenderBox; Bù đắp localPosition = box.globalToLocal(globalPosition);
double px = localPosition.dx; double py = localPosition.dy; if (!useSnapshot) { double widgetScale = box.size.width / photo!.width; print(py); px = (px / widgetScale); py = (py / widgetScale); } int pixel32 = photo!.getPixelSafe(px.toInt(), py.toInt()); int hex = abgrToArgb(pixel32); _stateController.add(Color(hex));
}
Tương lai loadImageBundleBytes() async { ByteData imageBytes = đang chờ rootBundle.load(imagePath); setImageBytes(imageBytes); }
Tương lai loadSnapshotBytes() async { RenderRepaintBoundary boxPaint = paintKey.currentContext!.findRenderObject() doing RenderRepaintBoundary; //RenderObject? boxPaint = paintKey.currentContext.findRenderObject(); ui.Chụp ảnh = đang chờ hộpPaint.toImage();
ByteData? imageBytes = await capture.toByteData(format: ui.ImageByteFormat.png); setImageBytes(imageBytes!); capture.dispose();
}
void setImageBytes(ByteData imageBytes) { Danh sách giá trị = imageBytes.buffer.asUint8List(); tấm hình; ảnh = img.decodeImage(giá trị)!; } }
// lib lib image sieu used used format KML color, convert #AABBGGRR to #AARRGGBB normal int abgrToArgb(int argbColor) { int r = (argbColor >> 16) & 0xFF; int b = argbColor & 0xFF; trả về (argbColor & 0xFF00FF00) | (b<<16) | r; } `
can you send me the image.dart file code? thanks
import 'package:image/image.dart' as img;
@roipeker I tried using the code and placed an image inside an InteractiveViewer widget. However, when I zoom in, the color I obtain by tapping on the image is not accurate.
What can I do ?
@roipeker, yes... your explanation makes sense!! I am trying time to work in this theme. Really, I am learn Flutter now. If I have some news, I post here. Thanks!