2021年12月5日 星期日

DIT FFT 範例修改

參考原始碼: https://cnx.org/contents/qAa9OhlP@2.44:zmcmahhR@7/Decimation-in-time-DIT-Radix-2-FFT 

範例修改: mfft.c

#include "math.h"
#include "stdio.h"
void mfft(double *real, double *imag, int nT) {
  int m = 0;
  for (int i = 0; i < 30; i++) if (nT == (1 << i)) {
    m = i;
    break;
  }
  if ( m < 1) return;

  const int halfT = nT >> 1;
  for (int i = 1, j = 0; i < nT - 1; i++) {// bit-reverse
    int k = halfT;
    while (j >= k) {
      j -= k;
      k >>= 1;
    }
    j += k;
    if (i < j) {
      double temp = real[i];
      real[i] = real[j];
      real[j] = temp;
      temp = imag[i];
      imag[i] = imag[j];
      imag[j] = temp;
    }
  }
  double PIx2 = - M_PI * 2;
  for (int i = 0, nx2 = 1; i < m; i++) { // radix-2 DIT FFT
    int k = nx2;
    nx2 += nx2;
    double delta = PIx2 / nx2;
    double theta = 0.0;
    for (int j = 0; j < k; j++) {
      double c = cos(theta);
      double s = sin(theta);
      theta += delta;
      for (int n = j; n < nT; n += nx2) {
        int h = n + k;
        double w1 = c * real[h] - s * imag[h];
        double w2 = s * real[h] + c * imag[h];
        real[h] = real[n] - w1;
        imag[h] = imag[n] - w2;
        real[n] += w1;
        imag[n] += w2;
      }
    }
  }
}   

int main() {
  double x[16];
  int n = sizeof(x) / sizeof(x[0]);
  double y[n];
  for (int i = 0; i < n; i++) {
    x[i] = i;
    y[i] = 0;
  }
  mfft(x, y , n);
  for (int i = 0; i < n; i++) printf("%.3f,\t %.3f\n", x[i], y[i]);
 
  return 0;
}

編譯並執行: g++ mfft.c && ./a.out

後記: 改成 Javascript 版本 mfft.js :
    mfft = (real, imag, nT) => {
        let m = 0;
        for (let i = 0; i < 30; i++) if (nT == (1 << i)) {
            m = i;
            break;
        }
        if ( m < 1) return;       
        const halfT = nT >> 1;
        for (let i = 1, j = 0; i < nT - 1; i++) {// bit-reverse
            let k = halfT;
            while (j >= k) {
                j -= k;
                k >>= 1;
            }
            j += k;
            if (i < j) {
                let temp = real[i];
                real[i] = real[j];
                real[j] = temp;
                temp = imag[i];
                imag[i] = imag[j];
                imag[j] = temp;
            }
        }
        let PIx2 = - Math.PI * 2;
        for (let i = 0, nx2 = 1; i < m; i++) { // radix-2 DIT FFT
            let k = nx2;
            nx2 += nx2;
            let delta = PIx2 / nx2;
            let theta = 0.0;
            for (let j = 0; j < k; j++) {
                let c = Math.cos(theta);
                let s = Math.sin(theta);
                theta += delta;
                for (let n = j; n < nT; n += nx2) {
                    let h = n + k;
                    let w1 = c * real[h] - s * imag[h];
                    let w2 = s * real[h] + c * imag[h];
                    real[h] = real[n] - w1;
                    imag[h] = imag[n] - w2;
                    real[n] += w1;
                    imag[n] += w2;
                }
            }
        }
    };   
    let n = 16;
    let x = new Float32Array(n);
    let y = new Float32Array(n);
    for (let i = 0; i < n; i++) {
        x[i] = i;
        y[i] = 0;
    }
    mfft(x, y , n);
    for (let i = 0; i < n; i++) console.log(`${Math.floor(x[i]*1e3 + 0.5)/1e3},\t ${Math.floor(y[i]*1e3 + 0.5)/1e3}`);
       

2021年10月19日 星期二

關於 atmeg328p 的 adc (類比轉數位) 取樣速率

當 atmega328p 使用 16MHz 振盪器運作時, adc clcok 除頻器設定可調整成 125kHz, 250kHz, 500kHz, 1MHz, 2MHz, 4MHz, 而完成一次 adc 取樣(sample)約需 13.5 個 adc clock cycle, 因此理論上每秒可以達到 9.259k, 18.518k, 37k, 74k, 148k, 296k 的取樣率(SPS:sample per second), 當 adc clcok 小於 1M 時, 用 cpu 搬動記憶體的速度遠比 adc 的取樣時間還快, 在 1M adc clock(含)以下, 達到 74k SPS 應該不成問題. 一旦超過像是 2M 或 4M 時, 記憶體搬動速度, 開始小於取樣時間, 取樣間隔將由記憶體搬動的時間為準, 除非有 DMA 加速, 否則用 CPU 搬動是一大瓶頸, 我實驗的結果, 保守估計:2M adc clock 時應可達到 140k SPS, 但 4M adc clock 時雜訊增多, 無法正確估計. 另外 2k 記憶體是造成取樣時間無法持續太久的主因, 但若是用來分析像是AC 110V/220V 50/60Hz 市電(通過電阻分壓器降壓至 5V 以下), 應當能勝任,且綽綽有餘.

備註:

如果固定使用 1000 取樣點作為示波器, 觀看波形: 當使用取樣率 fs = 100kHz 時, 每個取樣點間隔是 1/100k 秒, 1000 點就等於 1k * (1/100k) = 1/100 秒 = 0.01 秒 , 用來紀錄 100Hz 的波形時, 這 1000 點的時間(0.01 秒), 正好看到一個週期(1/100). 若提高取樣率成 2 倍, 例如 fs = 200k, 則一個取樣點是 1/200k 秒, 取樣 1000 點等於 1k * (1/200k) = 0.005 秒, 同樣觀看 100Hz 的頻率時,時間只足以看到半個週期(0.005 秒 = 0.5/100), 以此類推, 4 倍取樣率取 1000 點, 就只能看到 1/4 周期 ...

實驗一個取樣 1280 點來看 60Hz 旋波(測量點, 只要空接一條長導線, 就能感應到市電 60Hz 的旋波), 切到 74k SPS (1M adc clock)取樣率時, 能完整紀錄一個周期, 切換成 2M adc clock 樣取時, 看到半周期, 拉到 4M 時約略看到 1/4 周期的旋波. 但測量出的週期時間約莫多出 24% 左右, 因此估計 atmega328p 最大取樣率可能落在  296/1.24 = 238.7k SPS 左右.

文章閱讀: http://www.openmusiclabs.com/learning/digital/atmega-adc/

2021年8月25日 星期三

X window 簡單的繪圖及貼圖程式

 主程式 main.cpp:

#include <opencv2/opencv.hpp> // should include first to prevent conflic
#include <opencv2/highgui.hpp>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xresource.h>
#include <X11/cursorfont.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
using namespace std;
typedef unsigned char u8int;
typedef const char*   c_str;
Display *display;
Window     child;
GC        gCtx;      
XColor     fgColor, black, white ,red, green, blue;
XImage  *bg = nullptr;
int width   = 0;
int height  = 0;
void drawRect(int x, int y, int w, int h){
    XDrawRectangle(display, child, gCtx, x, y, w, h);
}
void drawLine(int x1, int y1, int x2, int y2){
    XDrawLine(display, child, gCtx, x1, y1, x2, y2);
}
void drawArc(int x, int y, int w, int h, int ang1, int ang2){
    XDrawArc(display, child, gCtx, x, y, w, h, ang1 << 6, ang2 << 6);
}
void setColorMap(XColor &c, const char *str){
    auto colormap = DefaultColormap(display, 0);
    XParseColor(display, colormap, str, &c);
    XAllocColor(display, colormap, &c);
}
void fgSetColor(XColor &c) {
    fgColor = c;
    XSetForeground(display, gCtx, c.pixel);
}
void setBackGround(u8int *src=nullptr, int sw=0, int sh=0, int sch=0){
    if (bg) XDestroyImage(bg);
    XWindowAttributes info;
    XGetWindowAttributes(display, child, &info);
    Visual *v = info.visual;
    width  = info.width ;
    height = info.height;
    int depth = info.depth;
    int sline = sch * sw;
    int dline= 4 * width;
    int size = dline * height;
    u8int *fb = (u8int *)malloc(size);
    if(! fb) {
        bg = nullptr;
        return;
    }
    bg = XCreateImage(display, v, depth,
        ZPixmap, 0, (char *)fb, width, height, 32, 0
    );
    if(! bg) {
        free(fb);
        return;
    }
    if(! src) {
        memset(fb, 0, size);
        return;
    }
    u8int *rgb = src;
    int zch = sch > 4 ? 4 : sch;
    float xs = (float)sw / width;
    float ys = (float)sh / height;
    float fy = 0;
    for(int y = 0; y < height; y ++) {
        float fx = 0;
        int d = 0;
        int s = 0;
        for (int x = 0; x < width; x ++) {
            fb[d] = rgb[s];
            for(int z = 1; z < zch; z ++) fb[d + z] = rgb[s + z];
            fx += xs;
            d += 4;
            s = sch * (int)fx;
        }
        fy += ys;
        fb += dline;
        rgb = src + sline * (int)fy;
    }
    XPutImage(display, child, gCtx, bg, 0, 0, 0, 0, width, height);
}
void setBackGround(cv::Mat &cam){
    return setBackGround((u8int *)cam.data,
        cam.cols,
        cam.rows,
        cam.channels()
    );
}
void bgRestore(int x0, int y0, int sw = 0, int sh = 0) {
    if (x0 < width && y0 < height){
        if (x0 < 0) x0 = 0;
        if (y0 < 0) y0 = 0;
        if (x0 + sw >=  width) sw = width  - x0;
        if (y0 + sh >= height) sh = height - y0;
        if (sw > 0 && sh > 0) XPutImage(display, child, gCtx,
            bg, x0, y0,
            x0, y0, sw, sh
        );
    }
}
int main(int argc, char **argv){    
    if(argc < 2) {
        printf("no file\n");
        return 1;
    }
    cv::Mat jpeg = cv::imread(argv[1]);// 8UC3
    display = XOpenDisplay(getenv("DISPLAY"));
    if (! display)  {
        printf("X error\n");
        return 3;
    }
    child = XCreateSimpleWindow(display,
        XDefaultRootWindow(display),
        0, 0, 600, 600,
        0,// border width
        WhitePixel(display, 0),
        BlackPixel(display, 0)
    );
    XSetStandardProperties(display, child, "MyXapp",
        "Icon", None, nullptr, 0, nullptr
    );
    gCtx = XCreateGC(display, child, 0, 0);
    setColorMap(black , "#000000");
    setColorMap(white , "#FFFFFF");
    setColorMap(red   , "#FF0000");
    setColorMap(green , "#00FF00");
    setColorMap(blue  , "#0000FF");  
    unsigned char shape[8] = {0xff, 0xff,0xff, 0xff, 0xff, 0xff,0xff, 0xff};
    unsigned char mask[8]  = {0xff, 0xff,0xff, 0, 0, 0xff,0xff, 0xff};// 1: show, 0: hidden
    Pixmap shapeid = XCreateBitmapFromData(display, child, (char *)shape, 8, 8);
    Pixmap maskid  = XCreateBitmapFromData(display, child, (char *)mask, 8, 8);
    Cursor cursorID = XCreatePixmapCursor(display, shapeid, maskid, &green, &black, 0, 0);
    XDefineCursor(display, child, cursorID);
    XFreePixmap(display, shapeid);
    XFreePixmap(display, maskid);
    XFreeCursor(display, cursorID);
    XSelectInput(display, child, ExposureMask | ButtonPressMask);
    XMapWindow(display, child);
    XFlush(display);
    fgSetColor(red);
    XEvent evt;
    
    printf("press mouse right button to exit.\n");
    while(true) {
        XNextEvent(display, &evt);
        if (evt.type == Expose) {
            if(evt.xexpose.count > 0) continue; // until count = 0
            setBackGround(jpeg);
            int right = width  - 1;
            int down  = height - 1;
            int cx    = width  / 2;
            int cy    = height / 2;
            drawLine(0, cy, right, cy);
            drawLine(cx, 0, cx, down);
            drawArc(0, 0, right, down, 0, 360);
            drawRect(0, 0, right, down);
            bgRestore(cx - 100, cy - 100, 200, 200);
        }        
        if (evt.type == ButtonPress)  {
            if(evt.xbutton.button == 3) {
                printf("press right\n");
                break;
            }
        }
    }
    if (bg) XDestroyImage(bg);
    XFreeGC(display, gCtx);
    XDestroyWindow(display, child);
    XCloseDisplay(display);    
}

事先準備好一個 test.jpeg, 編譯程式並執行:

g++   main.cpp  -lX11  `pkg-config  --cflags  --libs  opencv4`  && ./a.out  test.jpeg

2021年8月20日 星期五

linux 上使用 opencv 抓取螢幕影像

 不囉唆, 直接看原始碼 x1.cpp:
#include <opencv2/opencv.hpp>
#include <X11/Xutil.h>
#include <stdio.h>
using namespace std;
int main(int argc, char ** argv){
    XWindowAttributes x = { 0 };// x window 屬性(參數群)
    auto display = XOpenDisplay(nullptr);   // 打開 x window server 的顯示器
    auto root = DefaultRootWindow(display); // get default root?
    XGetWindowAttributes(display, root, &x);// 取得螢幕參數
    int height = x.height;
    int width  = x.width;
    auto buffer = XGetImage(display, root,
        0, 0, width, height,
        AllPlanes, ZPixmap
    );// 全螢幕影像

    int cvType = CV_8UC4;// default 4 bytes's A,R,G,B
    switch (buffer->bits_per_pixel) {
        case 24: cvType = CV_8UC3; break;// 3 bytes
        case 16: cvType = CV_8UC2; break;// 2 bytes
        case  8: cvType = CV_8UC1; break;// 1 bytes
    }
    const char *name = "ScreenCapture";
    cv::namedWindow(name, cv::WINDOW_AUTOSIZE);
    cv::Mat m(height, width, cvType, buffer->data);  // 暫時包成 cv::Mat
    cv::imshow(name, m); // 顯示全螢幕影像

    cv::resize(m, m, cv::Size(), 0.25, 0.25, cv::INTER_LINEAR);// 長寬各縮 1/4
    XDestroyImage(buffer) ;// 釋放 buffer
    XCloseDisplay(display);// 關閉 x window session

    cv::imshow("1/16 Screen Shot", m); // 顯示 1/16 螢幕影像
    cv::waitKey(0);
}
用 g++ 編譯並執行程式:
     g++  x1.cpp  -lX11 `pkg-config  --cflags  --libs  opencv4`  &&  ./a.out

2021年8月12日 星期四

linux 上是使用 kolinc-jvm 編譯 kotlin 程式, 程式透過 jni 呼叫原生 c 函式

 1. 上官網 https://github.com/JetBrains/kotlin/releases/download/v1.5.21/kotlin-compiler-1.5.21.zip下載 kotlin 編譯器, 開啟終端機進入下載目錄, 將它解壓縮到目錄 kotlinc 內

       cd  Downloads  &&  unzip  kotlin-compiler-1.5.21.zip

2.  安裝 openjdk 11, 將程式安裝到 /usr/lib/jvm/java-11-openjdk-amd64

       sudo  apt-get  install  openjdk-11-jdk

3. 建一個專案 project 目錄(mkdir project), 在裏面編寫一個 Makefile 方便編譯及跑程式:
buildDir  = build
main_jar  = $(buildDir)/main.jar
jniLib_jar= $(buildDir)/jniLib.jar
libjni_so = $(buildDir)/libnative.so

jdk11     = /usr/lib/jvm/java-11-openjdk-amd64/include
openjdk   = -I$(jdk11)  -I$(jdk11)/linux
opencv2   = `pkg-config  --cflags  --libs  opencv4`

kotlinDir =  /home/mint/Downloads/kotlinc
ktc       = $(kotlinDir)/bin/kotlinc  -include-runtime
coroutine = -cp $(kotlinDir)/lib/kotlinx-coroutines-core.jar

javaLib   = $(coroutine):$(jniLib_jar)
cppLib    = -shared  -fPIC  $(openjdk)  $(opencv2)

run: main  native
    java  -Djava.library.path=$(buildDir)  $(javaLib):$(main_jar)  MainKt

main:  $(buildDir)  $(main_jar)

native: $(libjni_so)

$(main_jar): main.kt  $(jniLib_jar)
    $(ktc) -d  $@  $<  $(javaLib)

$(jniLib_jar): jniLib.kt
    $(ktc) -d  $@  $<  $(coroutine)

$(libjni_so): cpp/native.cpp
    g++  -o  $@  $<  $(cppLib)

$(buildDir):
    [ -d $@ ] || mkdir $@ && echo $@ exist already

clean:
    [ -d $(buildDir) ] || echo $(buildDir) not exist && rm -rf $(buildDir)

4. 在專案目錄內寫一個 kotlin 主程式碼 main.kt:

import  kotlinx.coroutines.*
import  jniLib.*
fun  main( ) {
  runBlocking{
     GlobalScope.launch(Dispatchers.IO) { // split nonblocking coroutine
          println("coroutine hello1")
     }
     NativeClass( ).hello( ) // call  jni  c  routine
  }
}

5.  在專案目錄內寫一個 kotlin 介面程式庫 jniLib.kt:

package jniLib
class NativeClass {  
  external fun hello( ) // external  function written in c++ language
  init {
      System.loadLibrary("native")
  }
}

6. 在專案目錄內再建個 cpp 子目錄(mkdir  cpp), 在 cpp 目錄內編寫一個用 c 寫的 jni  程式碼 cpp/native.cpp:

#include <stdio.h>
#include <jni.h>
#include <opencv2/opencv.hpp>
#include <opencv2/highgui.hpp>
const char *window = "mouseEvent";
void mouseEvent(int event, int x, int y, int flags, void *args){
    static cv::Scalar green(0,   255,   0);
    static cv::Scalar red(0,   0,   255);
    cv::Mat &image = *(cv::Mat *)args;
    switch(event) {
        case cv::EVENT_LBUTTONDOWN: // left mouse button click
                printf("left   click row: %d, col: %d\n", x, y);
                cv::circle(image, cv::Point(x, y), 2, red);
                cv::imshow(window, image);
                break;
        case cv::EVENT_RBUTTONDOWN:// right mouse button click
                printf("right  click row: %d, col: %d\n", x, y);
                cv::line(image, cv::Point(0, 0), cv::Point(x, y), green, 2, cv::FILLED);
                cv::imshow(window, image);
                break;
        case cv::EVENT_MBUTTONDOWN:// middle mouse button click
                printf("middle click row: %d, col: %d\n", x, y);
                break;
        case cv::EVENT_MOUSEMOVE:// mouse movment
                // printf("mouse move  row: %d, col: %d\n", x, y);
                break;
    }
}
extern "C" {
    JNIEXPORT void JNICALL Java_jniLib_NativeClass_hello(JNIEnv *env, jobject obj) {        
        cv::Mat fg(800, 800, CV_8UC3);// w x h
        cv::namedWindow(window, cv::WINDOW_NORMAL);
        cv::setMouseCallback(window, mouseEvent, &fg);
        cv::imshow(window, fg);
        cv::waitKey(0);//press any key to exit
    }
}

上述步驟 3,4,5,6 存檔後, 在專案目錄內開啟終端機執行 make run 跑看看, 不用 Gradle, 速度快多了

後記: 使用 cmake 語法, 編譯 c++ 動態程式庫 libnative.so:

1. 先在專案目錄內, 編輯一個 CMakeLists.txt:
cmake_minimum_required(VERSION 3.10.2)
project(native)
include_directories(
    /usr/lib/jvm/java-11-openjdk-amd64/include
    /usr/lib/jvm/java-11-openjdk-amd64/include/linux
    /usr/include/opencv4
)
set(opencvLib   -lopencv_core   -lopencv_highgui   -lopencv_imgproc)
add_library(native   SHARED   cpp/native.cpp)
target_link_libraries(native   PUBLIC   ${opencvLib})

2. 建一個子目錄 build, 在裏面執行 cmake .. 產生 Makefile, 接著 make 就能生成 libnative.so:
    [ -d build ] || mkdir   build
    cd   build   &&   cmake  ..  &&  make

2021年8月1日 星期日

關於 UTF8 與 Unicode

 UTF8 編碼是 1 ~ 4 bytes 字元編碼, Unicode 則是固定 4 bytes 字元編碼:

#include "stdio.h"
typedef unsigned char u8int;
typedef const char*   c_str;

void toHex(u8int *utf8) {
        if (utf8) {
            int wc = 0;// wide char unicode
            while (*utf8 != 0) {
                if ((*utf8 & 0xc0) == 0x80) { // 10 xx xxxx, bits accumulate
                    wc <<= 6;// self-shift 6 bits to left
                    wc |= *utf8 & 0x3f;// accumulate 6 bits
                } else {// ready to decode
                    if (wc > 0x80) printf("%5x, ", wc); // previous one

                    if (*utf8 < 0x80) { // 0x xx xxxx, 1 bytes utf8
                        printf("%2x, ", *utf8);// decode immediatelly
                        wc = 0; // reset to 0 to prevent overflow
                    } // decode later
                    else if (*utf8 < 0xe0) wc = *utf8 & 0x1f;// 2 bytes utf8, 5 bits begin +  6 bits later = 11 bits total                       
                    else if (*utf8 < 0xf0) wc = *utf8 &  0xf;// 3 bytes utf8, 4 bits begin + 12 bits later = 16 bits total                       
                    else                   wc = *utf8 &  0x7;// 4 bytes utf8, 3 bits begin + 18 bits later = 21 bits total                       
                }
                utf8 ++;
            }
            if (wc > 0x80) printf("%5x", wc);
        }
}

void putwc(int wc){ // putchar for wide char unicode
    if(wc < 0x80)       printf("%c", wc); // 7  bits: 0xxxxxxx
    else if(wc < 0x800) printf("%c%c",
            0xc0 | (wc >> 7),
            0x80 | (wc & 0x3f)
        ); // 11 bits: 110xxxxx 10xxxxxx , 5 + 6 bits
    else if(wc < 0x10000) printf("%c%c%c",
            0xe0 |  (wc >> 12),
            0x80 | ((wc >>  6) & 0x3f),
            0x80 |  (wc        & 0x3f)
        );// 16 bits: 1110xxxx 10xxxxxx 10xxxxxx, 4 + 6 + 6 bits
    else printf("%c%c%c%c",
            0xf0 | ((wc >> 18) & 7),
            0x80 | ((wc >> 12) & 0x3f),
            0x80 | ((wc >>  6) & 0x3f),
            0x80 |  (wc        & 0x3f)
        );// 21 bits: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx, 3 + 6 + 6 + 6 bits
}

void toHex(c_str utf8) { return toHex((u8int *)utf8); }
int main(){
    c_str src = "Taiwan,台灣,🀄,0123456789,\U0001F004";
    toHex(src);
    printf("\n%s\n", src);   
    putwc(0x1f004);
    printf("\n");
    return 0;
}

編譯並執行  g++ a.c && ./a.out

54, 61, 69, 77, 61, 6e, 2c,  53f0,  7063, 2c, 1f004, 2c, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 2c, 1f004
Taiwan,台灣,🀄,0123456789,🀄
🀄

2021年7月24日 星期六

在 linux 系統下簡單解碼 qrcode, 中文也可以

1. 使用 nanojpeg 先將 Jpeg 解成 bitmap, 到官網 http://keyj.emphy.de/nanojpeg 下載原始碼 nanojpeg.c :

      http://svn.emphy.de/nanojpeg/trunk/nanojpeg/nanojpeg.c

2. 接著用 quirc 將 bitmap 內的 qrcode 解碼, 到官網 https://github.com/dlbeer/quirc 下載所有原始碼:

     https://github.com/dlbeer/quirc/tree/master/lib 

3. 將上述下載的檔案全放入一個專案目錄內, 編寫一個簡單的 Makefile, 方便編譯及執行程式:

opencv = `pkg-config --cflags --libs opencv4`
LIBS = decode.o identify.o quirc.o version_db.o
run: a.out
    ./$<


a.out: qr.o $(LIBS)
    g++ $^ $(opencv)  -lfreetype  -o $@
%.o : %.c
    gcc -c $<
%.o : %.cpp
    g++ $(opencv)  -I/usr/include/freetype2 -std=c++17 -c $<
clean:
    rm -f a.out *.o
   
opencv-dev:
    sudo apt-get update
    sudo apt-get  install   libopencv-dev   libfreetype-dev

若尚未安裝好 libopencv 或 libfreetype 程式庫, 可以先執行 make  opencv-dev 安裝它


4. 最後, 用手機對準 qrcode 拍照存成檔案 sample.jpg,  寫個 c++ 程式 qr.cpp 測試一下:

#include <opencv2/core.hpp>
#include <opencv2/opencv.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/freetype.hpp>
#include "nanojpeg.c"
#include "quirc.h"
using namespace std;

int  main( ) {
    FILE *fin = fopen("sample.jpg", "rb");
    if (fin== nullptr) return 1;

    fseek(fin, 0L, SEEK_END);
    long len = ftell(fin);
    unsigned char *jpeg = (unsigned char *)malloc(len);
    if (jpeg == nullptr) {
        fclose(fin);
        return 2;
    } else {
        fseek(fin, 0L, SEEK_SET);
        fread(jpeg, 1, len, fin);
        fclose(fin);
    }

    njInit();
    if (njDecode(jpeg, len) == NJ_OK) free(jpeg);
    else {
        free(jpeg);
        return 3;
    }

    auto  font = cv::freetype::createFreeType2();// use freetype2, which is a pointer
    font->loadFontData("/usr/share/fonts/opentype/noto/NotoSerifCJK-Bold.ttc", 0);
    auto xRotate = [ ](const cv::Mat &src,
        double sx = 1.0,
        double sy = 1.0,
        int deg = 0) {// // scale first, then rotate, if deg > 0, counterclockwise
        cv::Mat dst;
        cv::resize(src, dst, cv::Size(), sx, sy, cv::INTER_LINEAR);
        if (deg != 0) cv::warpAffine(dst,
            dst,
            cv::getRotationMatrix2D(
                cv::Point2f(dst.cols/2.0, dst.rows/2.0),
                deg,
                1.0
            ),
            dst.size()
        );
        return dst;
    };
    auto toGrey = [ ](const cv::Mat &src) { // conver to 8 bits Y only
        cv::Mat dst;
        cv::cvtColor(src, dst, cv::COLOR_BGR2GRAY);
        return dst;
    };
    auto rgbBMP = xRotate(cv::Mat(njGetHeight(), njGetWidth(), CV_8UC3, njGetImage()),
        1.0/6,
        1.0/4,
        0
    );
    quirc *qr = quirc_new();
    if (qr) {
        auto grey = toGrey(rgbBMP);
        if (quirc_resize(qr, grey.cols, grey.rows) == QUIRC_SUCCESS) {
            int width  = grey.cols;
            int height = grey.rows;
            uint8_t *feed = quirc_begin(qr, &width, &height);
            uint8_t *greyLine = grey.ptr<uint8_t>(0); // line begin
            for (int j = 0; j < height; j++) {// scan every line
                for (int i = 0; i < width; i ++) *feed ++ = *(greyLine + i);// pixel feed one bye one
                greyLine += grey.cols; // next line begin
            }
            quirc_end(qr);
            int count = quirc_count(qr);// number of qrcode

            for (int i = 0; i < count; i ++) {
                quirc_data qData;
                quirc_code qCode;
                quirc_extract(qr, i, &qCode);
                int err = quirc_decode(&qCode, &qData);
                if (err == QUIRC_ERROR_DATA_ECC) {
                    quirc_flip(&qCode);
                    err = quirc_decode(&qCode, &qData);
                }
                if (err == QUIRC_SUCCESS) { // sucess
                    for (int j = 0; j < 4; j++) { // draw outline
                        auto &lba = qCode.corners[j];// line begin alias
                        auto &lea = qCode.corners[(j + 1) % 4];// line end alias
                        cv::line(rgbBMP,
                            cv::Point(lba.x, lba.y),
                            cv::Point(lea.x, lea.y),
                            cv::Scalar(0, 255, 0),
                            2
                        );
                    }                   
                    font->putText(rgbBMP,
                        string((char *)qData.payload),
                        cv::Point(0, grey.rows - 24 * (i + 1)),
                        24, // font height
                        cv::Scalar(0, 0, 255),// red color
                        -1, // solid line when thickness is negative
                        16, // to smooth
                        true
                    );
                    printf("[qrcode %d]-> %s <-[length = %d]\n", i, qData.payload, qData.payload_len);
                }
            }
        }
        quirc_destroy(qr);
    }

    njDone();
    cv::imshow("qrcode decode", rgbBMP);
    cv::waitKey(0);// show image, press any key to exit
    return 0;
}

執行 make run 看能不能解出 qrcode. 若不行, 可以調整 xRotate( ) 的長/寬放大率再試試看.

後記: 使用 freetype2 繪製文字(putText)時, 傳進去的影像型態 cv::Mat 必須使用 CV_8UC3 格式

   

2021年6月27日 星期日

程式流程小技巧

if { } else { } 是流程進行的分歧點, 當層級過多時, 程式碼的邏輯就變的難以理解, 例如像是:

          if(... ) {

                if(... ) {

                       if(... ) {

                                   ...

                       } else {

                       }

                } else { 

                       ...

                } ...

          } else if ( ) {

          } else if( ) {

          }

改用一次性的 while loop, 例如:

            do { 

                        if( ) { ... 

                                 break

                         } 

                        if( ) { ... 

                                break

                        } 

                        ...

             } while(false)

若沒有 do{ } while 指令, 只有 while { } 指令, 只要最後使用 break 來跳脫迴圈就可:

           while (true) {

                    if ( ) { 

                         ...

                         break

                     }  

                     ...

                    break

            } 

這樣就能將程式碼攤平, 變的較容易閱讀

2021年6月22日 星期二

使用 gradle 管理並編譯 kotlin 程式碼

1. 首先上 gradle  官網: https://gradle.org/releases 只要下載 binary-only 壓縮檔就可以了

2. 打開終端機, 把下載回來的檔案(gradle-7.1-bin.zip)解壓縮, 再將執行檔所在目錄加進 PATH 內, gradle 就安裝完成:

     cd Downloads  &&  unzip gradle-7.1-bin.zip

     PATH=$PATH:/home/mint/Downloads/gradle-7.1/bin

3.建個目錄儲存各種專案, 主程式目錄名稱, 同時也會被當成是專案名稱, 例如下述 example 次專案, 進入目錄內執行 gradle init 將會再當地目錄初始化成新專案, 接著跳出選項, 要依照畫面適當選取, 最後執行 gradle run 就能啟動樣版程式

     mkdir  Project  &&  cd Project

     mkdir  example  &&  cd example

     gradle init

     gradle run

觀察它所產生的檔案, 我打算利用 make 語法, 編寫一個簡短的 Makefile 方便以後初始化新專案,而不用每次初始化都要用選的:
# /home/mint/Project/Makefile
shellpwd  = $(shell pwd)
project  = `basename $(shellpwd)`
gradlebin  = /home/mint/Downloads/gradle-7.1/bin/gradle
templateKts  = /home/mint/Project/template.gradle.kts
templateApp  = /home/mint/Project/template.kt
appDir  = app
kotlinDir  = src/main/kotlin
targetDir  = $(appDir)/$(kotlinDir)
AppKt  = $(targetDir)/App.kt
settingsKts  = settings.gradle.kts
setProject  = "rootProject.name=(\"$(project)\")"
setInclude = "include(\"$(appDir)\")"
buildKts = $(appDir)/build.gradle.kts
ktsSetApp  = "application { mainClass.set(\"$(project).AppKt\") }"
ktsSetSrc = "sourceSets.main { java.srcDirs(\"$(kotlinDir)\") }"

name:
    @echo this is a kotlin project: $(project)

$(targetDir): $(appDir)/src/main
    @[ -d $@ ]|| mkdir $@

$(appDir)/src/main: $(appDir)/src
    @[ -d $@ ] || mkdir $@

$(appDir)/src: $(appDir)
    @[ -d $@ ] || mkdir $@

$(appDir):
    @[ -d $@ ] || mkdir $@

init: $(settingsKts) $(AppKt)
    @echo ... $(project) is ready

$(settingsKts):
    @if [ -f $@ ]; then echo .; else echo $(setProject)      > $@ && echo $(setInclude) >> $@; fi

$(AppKt): $(targetDir) $(buildKts)
    @if [ -f $@ ]; then echo .; else echo package $(project) > $@ && cat $(templateApp) >> $@; fi

$(buildKts):
    @if [ -f $@ ]; then echo .; else cp $(templateKts) $@ && echo $(ktsSetSrc) >> $@ && echo $(ktsSetApp) >> $@ ;fi
   
run:
    $(gradlebin)  run  -q  --args='arg1 arg2 arg3 ...'

build:
    $(gradlebin) build --offline

clean:
    rm -rf  build  $(appDir)/build  .gradle

再寫一個 /home/mint/Project/template.gradle.kts 樣版檔案, 用來產生 build.gradle.kts:

// template.gradle.kts
plugins {
    kotlin("jvm") version "1.5.10"
    application
}
repositories { mavenCentral() }
dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0") }

以及另一個  /home/mint/Project/template.kt 樣版範例, 用來產生 kotline 的程式碼:

// template.kt
import kotlinx.coroutines.*
import kotlin.coroutines.*
fun main() {
    runBlocking{
        launch {
            println("Hello")
        }
        launch {
            println("world")
        }
        launch {
            println("you are welcome")
        }
    }
}

將上述 3 個檔案各自存檔後, 在 Makefile 所在目錄內執行 make init, 或是使用參數 -f 指定 Makefile 位置, 上述 make init 用來先產生 settings.grtadle.kts, app/build.gradle.kts, 及 app/src/main/kotlin/App.kt 等範例代碼, 這樣 gradle run 就能編譯並且執行程式:

       make   init   -f   /home/mint/Project/Makefile

      gradle  run

不用寫程式, 就能讓上面的樣版程式跑起來, 夠神奇吧! 實際上是 gradle 先根據 build.gradle.kts 內容,先上網路倉庫(repositories), 找尋插件(plugins)將 kotlin 編譯程式  kotlin("jvm") version "1.5.10" 以及各種需要的相關(dependencies)程式庫,下載後存入硬碟, 再呼叫 kotlin 去編譯成 Java 代碼(byte code), 最後在 J(ava)V(irtual)M(achine 內執行程式. 一切全自動完成, 這就是 gradle 厲害的地方.

ps.

1. gradle 缺點是網路一定要通暢才能運作, 主要是 gradle daemon 要先跑起來.雖然可以用 gradle --offline build 離線編譯程式, 但沒了網路, 例如用 iptables 先把網路的輸出通道關閉:

    sudo  iptables  -A  OUTPUT  -m  owner  --gid-owner  mint  -j  REJECT

    gradle  --offline  build

他就無法編譯了?  這參數 --offline 目的就真的有點奇怪, 似乎名不正言不順.

 2. 如果用複製/貼上面的 Makefile 內容, 無法讓 make 運作, 可能是縮排(tab)的問題, 可以用文字編輯程式稍微修改, 因為 make 規定所有標的物的執行代碼(冒號: 下一行)必須要先縮排(tab)才能運作, 否則會出現錯誤訊息.

2021年6月13日 星期日

使用 kotlin 語言把 http 協定連線升級成 WebSocket 協定所需要的回應編碼: base64( sha1(clientKey) )

 參考資料: https://datatracker.ietf.org/doc/html/rfc6455

    // echo -n dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11 | sha1sum 
    // sha1Hex  : b37a4f2cc0624f1690f64606cf385945b2bec4ea
    // keyBase64: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
   
    val clientKey = "dGhlIHNhbXBsZSBub25jZQ=="  
    val guID      = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
    val base64sym = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
    val key_GUID  = "${clientKey.trim()}$guID"
    val binary    = MessageDigest.getInstance("SHA-1").digest(key_GUID.toByteArray())
    val sha1Hex   = StringBuilder()
    val keyBase64 = StringBuilder()
    val temp3 = IntArray(3)
    var accumulate = 0
    for (c in binary)  {
        sha1Hex.append(String.format("%02x", c))
        temp3[accumulate ++] = c.toInt()
        if (accumulate == 3) {
            accumulate = 0;
            keyBase64.append(base64sym[ (temp3[0] and 0xfc) shr 2])
            keyBase64.append(base64sym[((temp3[0] and 0x03) shl 4) + ((temp3[1] and 0xf0) shr 4)])
            keyBase64.append(base64sym[((temp3[1] and 0x0f) shl 2) + ((temp3[2] and 0xc0) shr 6)])
            keyBase64.append(base64sym[  temp3[2] and 0x3f])
        }
    }
    if (accumulate > 0) {
        keyBase64.append(base64sym[ (temp3[0] and 0xfc) shr 2])
        if (accumulate == 2) {
            keyBase64.append(base64sym[((temp3[0] and 0x03) shl 4) + ((temp3[1] and 0xf0) shr 4)])
            keyBase64.append(base64sym[ (temp3[1] and 0x0f) shl 2])
        }
        keyBase64.append('=')
        if(accumulate == 1) keyBase64.append('=')
    }
    Log.d("WebSocket_key:", "$clientKey, $keyBase64, $sha1Hex") 

2021年6月10日 星期四

寫一個 Makefile 利用 openssl 產生自我簽章(selfsign)的證書(certification) 讓 Android SSLServerSocket 正常運作

將下列文字存成 Makefile, 複製到 Android 專案目錄內, 接著在終端機下指令 Make, 就會產生一個鑰匙庫(keystore.p12)放在 app/src/main/res/raw 目錄內, 而私鑰則放在 key 目錄下:
#Makefile
storepass=PKCS12_keystore_for_android_app
tempDir=/dev/shm
saveDir=app/src/main/res/raw
privateDir=key

gen: keystore.p12

keystore.p12: selfsign
    openssl pkcs12 -export -inkey $(privateDir)/prvkey.pem -passout pass:$(storepass) -in $(tempDir)/$< -out $(saveDir)/$@
    
selfsign: buildDirectory
    openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout $(privateDir)/prvkey.pem -subj "/C=TW/ST=Taiwan/L=TaipeiCity/CN=localhost" -out $(tempDir)/$@
    
buildDirectory:
    [ -d $(saveDir) ] || mkdir $(saveDir)
    [ -d $(privateDir) ] || mkdir $(privateDir)

clean:
    rm -rf $(tempDir)/selfsign

Android app 可以利用 resources 將它載入, 之後就能讓 SSL Server Socket 正常運作:

        // ...
        val tls = SSLContext.getInstance("TLS").apply {
            val keyStore = KeyStore.getInstance("PKCS12")
            val storepass = "PKCS12_keystore_for_android_app"//pass phrase define in Makefile
            resources.openRawResource(R.raw.keystore).apply {
                keyStore.load(this, storepass.toCharArray())
                close()
            }
            val kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()).apply {
                init(keyStore, storepass.toCharArray())
            }
            init(kmf.keyManagers, null, null)
        }
        val sslServer = tls.serverSocketFactory.createServerSocket(8443) .apply{
            setReuseAddress(true)
        }

如果寫的是 ssl client 端程式, 則要將信任網站的簽署檔(cert file) 放入 keystore, 交由 TrustManager 來管理, Android 可以使用 BKS 格式檔:

        val tls = SSLContext.getInstance("TLS").apply {
            val keyStore = KeyStore.getInstance("PKCS12")
            val storepass = "PKCS12_keystore_for_android_app"//pass phrase define in Makefile
            resources.openRawResource(R.raw.keystore).apply {
                keyStore.load(this, storepass.toCharArray())
                close()
            }
            val kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()).apply {
                init(keyStore, storepass.toCharArray())
            }
            val trustStore = KeyStore.getInstance("BKS")
            val storepass = "PKCS12_keystore_for_android_app"//pass phrase define in Makefile
            resources.openRawResource(R.raw.truststore).apply {
                trustStore.load(this, storepass.toCharArray())
                close()
            }
            val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).apply {
                init(trustStore)
            }
            init(kmf.keyManagers, tmf.trustManagers, SecureRandom())
        }

用 python 解簡單的常微分方程式

# sudo apt install python3-pip # python3 -m venv venv # cd venv # . bin/activate # pip3 install numpy matplotlib import numpy as np import m...