2022年8月18日 星期四
c++ 語言將陣列當成 bitmap 畫線方式
用 c++ 語言將 unsigned char arra[w * h] 陣列當灰階 bitmap, 在裏面畫線範例程式:
// draw.cpp
#include "stdio.h"
void drawline (unsigned char *p, int w, int h, int cch, int x1, int y1, int x2, int y2, int c=0xffffff) {
unsigned char *pixel = p + (y1 * w + x1) * cch;
int dx = x2 - x1;
int dy = y2 - y1;
int sx = dx > 0 ? 1 : -1;
int sy = dy > 0 ? 1 : -1;
int xbytes = cch * sx;
int ybytes = w * cch * sy;
if (dx < 0) dx = -dx;// abs, positive
if (dy > 0) dy = -dy;//-abs, negative
int de = dx + dy;
while (true) {
if (0 <= x1 && x1 < w &&
0 <= y1 && y1 < h) {
if (cch == 1) *pixel = c & 0xff;
else {
pixel[2] = c >> 16 & 0xff;// R
pixel[1] = c >> 8 & 0xff;// G
pixel[0] = c & 0xff;// B
}
}
if (x1 == x2 && y1 == y2) break;
int ex2 = de << 1;
if (ex2 >= dy) {
if (x1 == x2) break;
de += dy;
x1 += sx;
pixel += xbytes;
}
if (ex2 <= dx) {
if (y1 == y2) break;
de += dx;
y1 += sy;
pixel += ybytes;
}
}
};
int main( ){
int w = 80;
int h = 25;
unsigned char bitmap[w * h] ={0};// bitmap: width * height, gray set black color
auto grayDraw= [&](int x1, int y1, int x2, int y2) {
int cch = 1;// color channel
int grayLevel = 0xff;// white color
drawline(bitmap, w, h, cch, x1, y1, x2, y2, grayLevel);
};
grayDraw( 0 , 0, 0, h-1);
grayDraw( 0, 0, w-1, 0);
grayDraw( 0, h-1, w-1, h-1);
grayDraw(w-1, 0, w-1, h-1);
grayDraw( 0, 0, w-1, h-1);
grayDraw( 0, h-1, w-1, 0);
unsigned char *pixel = bitmap;
for(int y = 0; y < h; y++) {
for(int x = 0; x < w; x ++){
*pixel ++ > 0 ? printf("O") : printf("."); // 文字模式
}
printf("\n");
}
}
編譯並執行 g++ draw.cpp && ./a.out
2022年8月10日 星期三
Linux 上讓 Flutter 3.0 支援 opencv
opencv 用在影像處理上, 非常方便, 且原始碼用 c++ 寫的, dart 語言透過 ffi 便能與 c 語言相互交流, 近來
Flutter 3.0 支援 linux desktop 也趨成熟, 運用 cmake 結合 c 程式庫, 在 linux 上寫 GUI 程式,
用來處理影像就不再是難事.
1. 先用終端機建立一個專案
flutter create project1
2.在專案的 lib 目錄下建立一個 cpp 目錄, 將 c 原始碼放於此, 另外 build 目錄放編譯時的檔案
cd project1/lib && mkdir cpp && mkdir build
3. 安裝 opencv-dev 開發程式庫及 cmake, pkg-config 等工具程式:
sudo apt install libopencv-dev cmake pkg-config
4. 在 lib 目錄下建立一個檔案 CMakeLists.txt, 將以下內容存檔:
# lib/CMakeLists.txt
cmake_minimum_required(VERSION 3.16.3)
project("native")
find_package(PkgConfig REQUIRED)
pkg_check_modules(OpenCV REQUIRED IMPORTED_TARGET opencv4)
include_directories(
cpp
${OpenCV_INCLUDE_DIRS}
)
add_library(native SHARED
cpp/native.cpp
)
target_link_libraries(native
${OpenCV_LIBRARIES}
)
5. 編輯 porject1/lib/cpp/native.h , porject1/lib/cpp/native.cpp 示範呼叫 opencv:
// lib/cpp/native.cpp
#include <vector>
#include "native.h"
using namespace std;
using namespace cv;
extern "C" { // export as C function
ImgStruct grayJpeg(unsigned char *bin = nullptr, int size = 0) {// 解碼 bin, 轉成 jpeg 灰階影像
static vector<unsigned char> jpg;
vector<unsigned char>().swap(jpg);
if (bin == nullptr) return ImgStruct {.size = 0, .data = nullptr};
imencode(".jpg",
imdecode(Mat(1, size, CV_8UC1, bin), IMREAD_GRAYSCALE),
jpg
);
return ImgStruct {
.size = (int) jpg.size(),
.data = jpg.data()
};
}
}
// lib/cpp/native.h
#ifdef __cplusplus
extern "C" {
#endif
#ifndef Native_H
#define Native_H
struct ImgStruct {
int size;
unsigned char *data;
};
ImgStruct grayJpeg(unsigned char*, int);
#endif
#ifdef __cplusplus
}
#endif
6.進入 project1/linux 建好程式庫目錄的聯結:
cd porject1/linux && ln -sf ../lib .
接著編輯 project1/linux/CMakeLists.txt, 拉到最後面, 加入以下文字:
add_subdirectory("./lib")
set(nativeSO "${PROJECT_BINARY_DIR}/lib/libnative.so")
install(FILES ${nativeSO} DESTINATION ${INSTALL_BUNDLE_LIB_DIR} COMPONENT Runtime)
7. 編輯主程式 project1/lib/main.dart 及繪圖程式 project1/lib/mjpgViewer.dart:
//主程式: lib/main.dart
import 'package:flutter/material.dart';
import 'mjpgViewer.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark(),
home: const HomePage(),
title:"MjpgViewer"
);
}
}
class HomePage extends StatelessWidget{
const HomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: MjpgViewer(MediaQuery.of(context).size)
);
}
}
// 繪圖程式: lib/mjpgViewer.dart
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'dart:ui' as ui;
import 'dart:ffi' hide Size;
import "package:ffi/ffi.dart";
import 'package:flutter/services.dart';
typedef CharPtr = Pointer<Uint8>;
class ImgStruct extends Struct {
@Int32() external int length;
external CharPtr data;
Uint8List asTypedList() => data.asTypedList(length); // method to get data
}
class MjpgViewer extends StatefulWidget {
final Size bodySize;
MjpgViewer(Size? size): bodySize = size ?? Size(400, 400);
@override _mjpgViewerState createState() => _mjpgViewerState();
}
class _mjpgViewerState extends State<MjpgViewer> {
ui.Image? assetsImage;
@override void initState( ) {
super .initState( );
final libso = DynamicLibrary.open("libnative.so");// load share library
final grayJpeg = libso.lookupFunction<ImgStruct Function(CharPtr, Int),
ImgStruct Function (CharPtr, int)
>("grayJpeg");
rootBundle.load("assets/test.jpg").then((ByteData bytes) {
final Uint8List u8List = bytes.buffer.asUint8List();
final int len = u8List.lengthInBytes;
final CharPtr jpg = malloc.allocate<Uint8>(len);//預先分配動態記憶空間
jpg.asTypedList(len).setAll(0, u8List);// 因無法獲得 u8List 的指標, 只能複製後再傳過去, 從 u8List 位置 0 開始, 複製到底.
final ImgStruct img = grayJpeg(jpg, len);
decodeImageFromList(img.asTypedList( )).then(
(_uiImg) => setState(()=> assetsImage = _uiImg)
);
malloc.free(jpg);// 動態分配的空間, 不用時必須釋放掉
});
}
@override Widget build(BuildContext context) => CustomPaint(
painter: CavasDraw(assetsImage),
size: widget.bodySize,
);
}
class CavasDraw extends CustomPainter {
final ui.Image? background;
CavasDraw(this.background);
@override bool shouldRepaint(CavasDraw old) => true;
@override void paint(Canvas canvas, Size size){
final bg = background;
if (bg != null) {
final pen = Paint();
pen.strokeWidth = 1;
pen.style = PaintingStyle.stroke;
final src = Rect.fromLTWH(0, 0, bg.width.toDouble(), bg.height.toDouble());
final dst = Rect.fromLTWH(0, 0, size.width, size.height);
canvas.drawImageRect(bg, src, dst, pen);
}
}
}
// project1/pubspec.yaml
name: project1
description: A new Flutter project.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1
environment:
sdk: ">=2.17.6 <3.0.0"
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
ffi: ^2.0.1
flutter:
assets:
- assets/test.jpg
uses-material-design: true
最後進入專案 project1 的目錄, 建個子目錄 assets, 將 test.jpg 放進該目錄內, 執行:
cd project1 && flutter pub get && flutter run --no-version-check
後記:
1. 整個專案目錄結構
project1/
pubspec.yaml
assets/
test.jpg
lib/
main.dart
mjpgViewer.dart
CMakeLists.txt
CMakeLists.android
cpp/
native.cpp
native.h
linux/
CMakeLists.txt
lib -> ../lib
android/
lib/
CMakeLists.txt -> ../../lib/CMakeLists.android
cpp -> ../../lib/cpp
app/
build.gradle
# CMakeLists.android
cmake_minimum_required(VERSION 3.16.3)
project("native")
set(OpenCV_DIR /home/mint/Downloads/OpenCV-android-sdk/sdk/native/jni)
find_package(OpenCV REQUIRED)
include_directories(
cpp
${OpenCV_INCLUDE_DIRS}
)
add_library(native SHARED
cpp/native.cpp
)
target_link_libraries(native
-ljnigraphics
${OpenCV_LIBS}
)
但 cmake 只認得 CMakeLists.txt, 因此用 ln -sf 符號聯結的方式來解決檔名的問題, 也不需複製.
cd Project1/android && mkdir lib && cd lib
ln -sf ../../lib/cpp .
ln -sf ../../lib/CMakeLists.android CMakeLists.txt
最後編輯
project1/android/app/build.gradle, 加入 externalNativeBuild, 將
CMakeLists.txt 路徑設定好就可以了:
android {
//...
externalNativeBuild {
cmake {
path "../lib/CMakeLists.txt"
}
}
}
p.s. 編譯成 Android 可以用的 share library, 參考文章:
https://levelup.gitconnected.com/port-an-existing-c-c-app-to-flutter-with-dart-ffi-8dc401a69fd7
2022年8月6日 星期六
測試dart 的 c interop 並繪圖
//main.dart
import 'package:flutter/material.dart';
import 'draw2d.dart';
void main( ) {
runApp(new MyApp( ));
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark( ),
home: HomePage( ),
title:"draw2d",// id, it's not title name!
);
}
}
class HomePage extends StatelessWidget{
@override
Widget build(BuildContext context) {
return Scaffold(
body: Draw2D(MediaQuery.of(context).size)
);
}
}
// draw2d.dart
import 'dart:ffi' hide Size;// to use Size in dart:ui
import 'dart:math';
import 'dart:ui';
import 'package:flutter/material.dart' hide TextStyle;// to use TextStyle in dart:ui
import "package:ffi/ffi.dart";
import "dart:async";
import "dart:isolate";
class Draw2D extends StatefulWidget {
final Size bodySize;
Draw2D(Size? size): bodySize = size ?? Size(400, 400);
@override _Draw2DState createState( ) => _Draw2DState( );
}
class _Draw2DState extends State<Draw2D> {
List<double> figure = [ ];
final iso = ReceivePort( );
final dataOffset = 16;
static Future<void> bgIsolate(SendPort port) async { // isolate function must be static
final tag = Isolate.current.debugName.toString( );
final tagLog= (_)=> print("${DateTime.now( )}(${tag}) $_");
final libso = DynamicLibrary.open("lib/libnative.so");// load share library
final sttyReady = libso.lookupFunction<Bool Function( ),
bool Function ( )
>("ready");
final sopen = libso.lookupFunction<Int32 Function(Pointer<Utf8>, Int32),
int Function(Pointer<Utf8>, int)
>("sopen");
final sttyOpen = ([String dev = "/dev/ttyACM0", baud = 250000]) {
final _str = dev.toNativeUtf8( );
sopen(_str, baud);
malloc.free(_str);
};
final swrite = libso.lookupFunction<Int32 Function(Pointer<Utf8>),
int Function(Pointer<Utf8>)
>("swrite");
final sttyWrite = (String str) {
final _str = str.toNativeUtf8( );
swrite(_str);
malloc.free(_str);
};
final sttyPoll = libso.lookupFunction<Int32 Function( ),
int Function ( )
>("spoll");
final sttyBuffer = libso.lookupFunction<Pointer<Uint8> Function( ),
Pointer<Uint8> Function ( )
>("adcBuffer");
final portSendADC = ( ) {
final n = sttyPoll( );
if (n > 0) {// make sure null safety
port.send(sttyBuffer( ).asTypedList(n));
}
};
tagLog("debugName is $tag\n");
if (!sttyReady( )) sttyOpen("/dev/ttyACM0");
if (sttyReady( )) {
sttyWrite("fson\n");// command to send ADC data
while (true) {// event loop
await Future.delayed(Duration(milliseconds: 0));
portSendADC( );
}
sttyWrite("fsoff\n");// command to turn off ADC
}
Isolate.exit(port, null);// isolate bgChild exit and return a null back.
}
@override void dispose( ) {
iso.close( );
super.dispose( );
}
@override void initState( ) {
super.initState( );
final mainLog = (_) => print("${DateTime.now( )}(${Isolate.current.debugName}) $_");
mainLog("Future<Isolate> attach sendPort");
figure = List<double>.filled(widget.bodySize.width.toInt( ), 0);
final flen = figure.length;
for (int i = 0; i < flen; i ++) {// normalize f(θ) = (1 + sin(θ)) / 2;
figure[i] = (1 - sin(2 * 3.14159 * i / flen)) / 2;// normallize, mirror to speed up CavasDraw.paint
}
Isolate.spawn(bgIsolate, iso.sendPort, debugName: "bgChild");
iso.listen((adcData) {
if (adcData == null) {
iso.close( );
} else {
int i = 0;
for (final adc in adcData) {
if (i < dataOffset) figure[i] = 0.5;// header, fill base point 128 / 256 = 0.5
else if (i < flen) figure[i] = adc / 256.0;// normallize adc data, mirror already.
i ++;// next location
}
setState(( ){ });
}
});
}
@override Widget build(BuildContext context) => Listener (
onPointerSignal: (evt) => setState(( ) {
// print(evt);
}),
onPointerHover: (evt) => setState(( ) {
// print(evt);
}),
child: CustomPaint(
painter: CavasDraw(widget.bodySize, figure),
size: widget.bodySize,
)
);
}
class CavasDraw extends CustomPainter {
final Size drawSize;
final List<double> figure;// normalize/mirror figure [0 ~ 1]
final pen = Paint( );
final outline = Path( );
final dx, cx, cy, w, h;
late Offset east, west, south, north, center;
Paragraph sentence(String str, [double? sz, Color? c]) {
final font = ParagraphBuilder(ParagraphStyle(fontSize: sz ?? 24))..
pushStyle(TextStyle(color: c ?? Colors.white))..
addText(str);
return font.build( ).. layout(ParagraphConstraints(width: drawSize.width));
}
CavasDraw(this.drawSize, this.figure):
dx = drawSize.width / figure.length,
cx = drawSize.width / 2,
cy = drawSize.height / 2,
w = drawSize.width,
h = drawSize.height {
pen.style = PaintingStyle.stroke;
center = Offset(cx, cy);
east = Offset(w - 1, cy);
west = Offset(0, cy);
south = Offset(cx, h - 1);
north = Offset(cx, 0);
outline.moveTo(0, 0);
outline.lineTo(0, h - 1);
outline.lineTo(w - 1, h - 1);
outline.lineTo(w - 1, 0);
outline.lineTo(0, 0);
outline.close( );
}
@override bool shouldRepaint(CavasDraw old) => true;
@override void paint(Canvas canvas, Size size) {
pen.strokeWidth = 1;
pen.color = Colors.green;
double x = 0.0;
Offset lastone = Offset(x, cy);// base point
for (final y in figure) {// draw the figure
final fxy = Offset(x, h * y);
canvas.drawLine(lastone, fxy, pen);
lastone = fxy;
x += dx;
}
pen.color = Colors.red;
canvas.drawLine(north, south, pen);
canvas.drawLine(west, east, pen);
canvas.drawParagraph(sentence("觸發點0"), west);// draw text
pen.strokeWidth = 8;
pen.color = Colors.grey;
canvas.drawPath(outline, pen);
}
}
#CMakeLists.txt
cmake_minimum_required(VERSION 3.7)
project(native)
add_library(native SHARED
native.cpp
)
//native.h
#ifdef __cplusplus
extern "C" {
#endif
#ifndef Native_H
#define Native_H
bool ready( );// device ready
int sopen(char *buffer, int baud);
int spoll( );// number of data available in serial port
int swrite(char *buffer);// serial port write c_str
unsigned char *adcBuffer( );
#endif
#ifdef __cplusplus
}
#endif
//native.cpp
#include "UBitSerial2.h"
#include "native.h"
static UBitSerial serialPort = UBitSerial( );
extern "C" { // export as C function
bool ready( ) {
return serialPort.ready( );
}
int spoll( ) {
return serialPort.adcPoll( );
}
int swrite(char *buffer){
return serialPort.write(buffer);
}
int sopen(char *buffer, int baud) {
return serialPort.sopen(buffer, baud);
}
unsigned char *adcBuffer( ) {
return serialPort.adcBuffer( );
}
}
//UbitSerial2.h
#ifndef UBitSerial2_H
#define UBitSerial2_H
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <asm/termbits.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#define defaultBaudrate 250000
#define maxCountSPF 2048
#define dataOffset 16
class UBitSerial {
private:
int fd = 0;
public:
~UBitSerial( ) {
if (fd) close(fd);
fd = 0;
}
int sopen(char *dev, int baud) {
if (fd > 0) close(fd);
int ttyFD = open(dev, O_RDWR | O_NONBLOCK);
if (ttyFD > 0) {
fd = ttyFD;
struct termios2 uart2;
ioctl(fd, TCGETS2, &uart2);
uart2.c_iflag = 0;
uart2.c_oflag = 0;
uart2.c_lflag = 0;
uart2.c_cc[VMIN] = 1;
uart2.c_cc[VTIME] = 0;
uart2.c_cflag = CS8 | CREAD | CLOCAL;
uart2.c_cflag &= ~CBAUD;
uart2.c_cflag |= CBAUDEX;
uart2.c_ispeed = baud;
uart2.c_ospeed = baud;
ioctl(fd, TCSETS2, &uart2);
::printf("%s baud=%d ready. serial port fd=%d\n", dev, baud, fd); // use global printf
}
return ttyFD;
}
UBitSerial(const char *dev = "/dev/ttyUSB0", int baud = defaultBaudrate) {
sopen((char *)dev, baud);
}
bool ready( ) { return fd > 0; }
unsigned char rawBuffer[2][maxCountSPF];// byte buffer
int qSize = sizeof(rawBuffer)/sizeof(rawBuffer[0]);
int head = 0;
int tail = 0;
int frameSync = 0;
int nIndex = 0;
char chunkBuffer[1024];
const int lastone = sizeof(chunkBuffer) - 1;
int adcPoll( ) {
if (fd > 0) {
int fig_1 = 0;
int chunk = 0;
unsigned char *fillBuffer = rawBuffer[head];// raw figure buffer
while (true) {
ioctl(fd, FIONREAD, &chunk);
if (chunk <= 0) break;// poll until empty
if (chunk > lastone) chunk = lastone;
::read(fd, chunkBuffer, chunk);
chunkBuffer[chunk] = 0;// append EOS
if (strstr(chunkBuffer, "stop!!!")) continue;
for (int i = 0; i < chunk; i ++) {// check one by one
unsigned char figY = (unsigned char) chunkBuffer[i];
switch (frameSync) {// state macine to detect frameSync 0xff 0x0 ...
case 0: if (figY == 0xff) {
frameSync = 1;
fig_1 = 0xff;
}
break;
case 1: if ((figY & 0xf0) != 0 || fig_1 != 0xff) frameSync = 0;
else {
frameSync = 2;
nIndex = 0;
fillBuffer[nIndex ++] = 0xff;//insert 0xff at the begining
}
break;
default:break;
}
if (frameSync != 2) continue;// until frameSync == 2
if (nIndex < dataOffset) fillBuffer[nIndex ++] = figY;
else fillBuffer[nIndex ++] = ~figY;
if (nIndex == maxCountSPF) {
frameSync = 0;
int next = head + 1;
if (next == qSize) next = 0;
if (next != tail) head = next;// head to next rawBuffer
}
}
}
}
return (head == tail) ? 0 : maxCountSPF;
}
unsigned char *adcBuffer( ) {// no copy
if (adcPoll( ) > 0) {
unsigned char *ptr = rawBuffer[tail];
if (++ tail == qSize) tail = 0;
return ptr;
}
return nullptr;
}
int write(char *buffer) {
if (fd && buffer) return ::write(fd, buffer, strlen(buffer));// use global write
return 0;
}
int printf(const char *fmt, ...) {// todo: prevent size of strBuffer overflow.
static char strBuffer[1024];// upto 1024 characters
va_list parameters;
va_start(parameters, fmt);
vsprintf(strBuffer, fmt, parameters);
va_end(parameters);
return write(strBuffer);// this->write
}
};
#endif
2022年8月3日 星期三
關於 flutter Matrix4 部件
Flutter 可以將部件用 Matrix4 包裝起來, 透過它將部件作旋轉, 位移, 放大等特效.可以參考文章:https://medium.com/flutter-community/advanced-flutter-matrix4-and-perspective-transformations-a79404a0d828
1. 旋轉 X 軸時, 使用的轉換矩陣是[1,0,0,0; 0, cos, sin, 0; 0,-sin, cos, 0; 0,0,0,1], 座標運算一下:
x' = x, y' = y * cos(θ) + z * sin(θ), z' = -y * sin(θ) + z * cos(θ)
因為畫面 z = 0 => x' = x, y' = y * cos(θ), 可以預見 X 軸不變, 但 Y 軸變短了, 因為|cos(θ)| <= 1
2. 旋轉 Y 軸時, 使用的轉換矩陣是[cos,0,-sin,0; 0, 1, 0, 0; sin,0,cos,0; 0,0,0,1], 座標運算一下:
x' = x * cos - z *sin, y' = y, z' = x * sin + z *cos
因為畫面 z = 0 => x' = x * cos, y' = y , 可以預見 Y 軸不變, 但 X 軸變短了, 因為|cos(θ)| <= 1
3.旋轉 Z 軸, 使用的轉換矩陣是[cos, sin,0, 0; -sin,cos,0,0; 0,0,1, 0; 0,0,0,1], 座標運算一下:
x' = x * cos(θ) + y *sin(θ), y' = -x * sin(θ) + y * cos(θ), z' = z
當所有座標點, 以左上角當原點 (0,0, 0)為參考點, 同時旋轉旋轉 Z 軸 θ 時, 各座標點相對位置將一如往常, 物體並不會變形
Linux mint 玩 waydroid 一些心得
1. 目前使用 linux mint 22.1 作業系統可以順利跑起來, 可上官網去下載, 並安裝到硬碟. 2. 安裝 waydroid 可上網站 https://docs.waydro.id 參考看看: https://docs.waydro.id/usage/inst...
-
1. 上 Firebase 網站 註冊啟用 Firebase : https://firebase.google.com/ 2. 進入 Firebase Console 註冊開啟新專案 : https://console.firebase.google.com/...
-
Flutter 讓人很容易短時間就能開發出 app, 關鍵在於他開發了很多種小部件稱為 Widget, 將Widget 組合起來就是一個 app. 所謂 部 件(Widget)就是一個可以呈現在螢幕上的視覺系 物 件,幾乎可以說 Flutter 每個物件都是 部 件. 開發者透...