Wails で Go → JS にデータを送る方法 (emit)

はじめに

この記事は Qiita Advent Calendar 2025 "Go シリーズ 2" の20日目の記事です。

参考資料

shinshin86.hateblo.jp

↑ ぶっちゃけ上に全部書いてます。

wails.io

本文

Wails で Go から JS にデータを送るには、Go 側では runtime.EventsEmit を使い、JS側では EventsOn を使います。

// app.go
func (a *App) startup(ctx context.Context) {
    a.ctx = ctx

    go func() {
        // 1秒ごとに現在時刻を送信する
        for {
            ct := time.Now().Format("15:04:05")
            runtime.EventsEmit(a.ctx, "current_time", ct)
            <-time.After(1 * time.Second)
        }
    }()
}
// frontend/src/App.jsx
import { useEffect, useState } from "react";
import logo from "./assets/images/logo-universal.png";
import "./App.css";
import { Greet } from "../wailsjs/go/main/App";
import { EventsOn, EventsOff } from "../wailsjs/runtime/runtime";

function App() {
  const [resultText, setResultText] = useState(
    "Please enter your name below 👇"
  );
  const [name, setName] = useState("");
  const [currentTime, setCurrentTime] = useState("");
  const updateName = (e) => setName(e.target.value);
  const updateResultText = (result) => setResultText(result);

  // ここがキモ
  useEffect(() => {
    EventsOn("current_time", (msg) => {
      setCurrentTime(msg);
    });

    return () => {
      EventsOff("current_time");
      console.log("unmounting...");
    };
  });

  function greet() {
    Greet(name).then(updateResultText);
  }

  return (
    <div id="App">
      <img src={logo} id="logo" alt="logo" />
      <div id="result" className="result">
        {currentTime}
      </div>
      <div id="result" className="result">
        {resultText}
      </div>
      <div id="input" className="input-box">
        <input
          id="name"
          className="input"
          onChange={updateName}
          autoComplete="off"
          name="input"
          type="text"
        />
        <button className="btn" onClick={greet}>
          Greet
        </button>
      </div>
    </div>
  );
}

export default App;

これで、現在時刻を 1秒周期でJSに送ることができます。1ms周期でも動くには動きますが、重くなるのでやめておいた方がいいかもしれません。ご利用は計画的に。

最近は、NFCカードリーダーがGoから使いやすくなってるんだね

はじめに

この記事は Qiita Advent Calendar 2025 "Go シリーズ 2" の1日目の記事です。

経緯

なんか、Go のアドカレ空いてるなー、なんか記事になるネタ無いかなーと思って色々探してたら、そういえばこのことまだ記事にしてなかったなと思ったので記事にしてみました。そんな大したことないのでほったらかしでしたが、アドカレってそういうのでいいんだよね。本来は。最近はみんな頑張って良記事書いてくるから敷居が上がっちゃって、だから、参加表明しても力尽きて投稿せずに空きが出るなんていう本末転倒なことになるわけで、こういう低レベルな記事を載せることで、みんなに「あ、こんなんでいいんだ」って思ってもらいたい。

本文

以前、こんな記事を書いてました。

bamch0h.hatenablog.com

PaSoRi っていうSony製のNFCカードリーダー・ライタを使って NFCカードのIDを読みだす方法を記事にしてました。今回も一緒です。ただ、前回は自作の golang ライブラリを作成して読みだしたんですが、今回は deeper-x/gopcsc を使います。これ、何が違うかって言うと、前者は Sony製のDLLを使うのに対して、後者はPC/SCって言う標準規格を使ってアクセスします。それによって、DLLに依存することなくカードリーダーが使えるっていうのがメリットです。世の中進歩してるんですね。以下コード

package main

import (
    "fmt"

    "github.com/deeper-x/gopcsc/smartcard"
)

func main() {
    fmt.Println("please touch NFC card to reader...")

    ctx, err := smartcard.EstablishContext()
    if err != nil {
        panic(err)
    }
    defer ctx.Release()

    reader, err := ctx.WaitForCardPresent()
    if err != nil {
        panic(err)
    }

    card, err := reader.Connect()
    if err != nil {
        panic(err)
    }
    defer card.Disconnect()

    fmt.Printf("Card ATR: %s\n", card.ATR())
    command := smartcard.Command2(0xFF, 0xCA, 0x00, 0x00, 0x00) # ここがミソ
    response, err := card.TransmitAPDU(command)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Response: %X\n", response.Data())
}

smartcard.Command2 を使って APDUコマンドを作成しているところがミソ。コマンド詳細は ストレージ カードの要件 - Windows drivers | Microsoft Learn ←これを参考にした気がする。ただ、だいぶ昔のことなので本当にここを参考にしたかはちゃんと覚えてない。

何しか、このコードを実行して、パソリに適当な NFC を読ませると値が返ってくるはず・・・っ!!

まとめ

PCSCのライブラリを使って PaSoRi で NFC を読んでみるサンプルの紹介でした。

みんな気軽に記事を投稿しようぜ!!

以上。

ラズパイで Go 製のツールを ps で見たときに大量のメモリを使ってるように見えてもびっくりしないで

はじめに

この記事は Qiita Advent Calendar 2025 "Go シリーズ 1" の14日目の記事です。

本文

github.com

Go製の FTPサーバーに fclairamb/ftpserver というのがあります。私はよくお世話になっているのですが、以下のイシューが上がっていました。

github.com

内容は「このツールを arm64 の環境で実行したらメモリ1GBも使ってるけどなんで?」って内容。

私がすでに回答していますが、この件、すでに Go の公式の FAQに乗っているくらい有名なことのようです。

go.dev

ザックリいうと、Go は仮想メモリ領域をはじめにガッツリ予約領域として確保しているみたいなんです。その仮想メモリの領域が ps とかで見たときにでかい領域を確保しているように見えてしまいます。

ただ、ps のメモリ表示には二種類あって、VSZ が 予約領域サイズで、実メモリサイズは RSS の値になります。なので、実メモリサイズを見たい場合は RSS を見るのがよさそうです。

シンプルなプログラムで実験

単純に入力待ちをするプログラムを C言語と Go言語で書いてみました。環境はラズパイではなく Ubuntu (Intel Core i7) ですが、この現象はラズパイのみの現象ではないので Ubuntu でも再現できます。

以下 C言語のプログラム

#include <stdio.h>

int main(void) {
    int v;
    scanf("%d", &v);
}

以下 Go言語のプログラム

package main

import "fmt"

func main() {
    var v int
    fmt.Scanf("%d", &v)
}

実行結果

$ ps aux | head -n 1; ps aux | grep virmemcheck | grep -v grep
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
bamchoh    11130  0.0  0.0   2684  1368 pts/2    S+   20:22   0:00 ./c-virmemcheck
bamchoh    11134  0.0  0.0 1225436 1820 pts/0    Sl+  20:23   0:00 ./go-virmemcheck

確かに、VSZ(仮想メモリ領域) がかなり大きくなっているのがわかります。ただ、RSS(実メモリサイズ)はさほど変わらないこともわかりますね。

まとめ

Go製のツールを ps で見るときは VSZ を見てびっくりしないようにしよう!

メモリ使用量を見たいときは RSS か RES を確認しよう!

fzf のファジーマッチアルゴリズム部分だけを抜き出して使う

はじめに

この記事は Qiita Advent Calendar 2025 "Go シリーズ 1" の9日目の記事です。

本文

言わずと知れた fzf という便利ツールのマッチアルゴリズム部分だけを使ってみよう。

github.com/junegunn/fzf/src/algo が本体。

以下がサンプルコード。意外と簡単に使えます。

package main

import (
    "fmt"

    "github.com/junegunn/fzf/src/algo"
    "github.com/junegunn/fzf/src/util"
)

func init() {
    algo.Init("default")
}

func main() {
    list := []string{
        "foobar",
        "foobaz",
        "barbaz",
        "barfoo",
        "bazfoo",
    }

    pattern := "foo"

    for _, text := range list {
        chars := util.ToChars([]byte(text))
        res, pos := algo.FuzzyMatchV2(false, false, false, &chars, []rune(pattern), true, nil)
        fmt.Println(text, res, pos)
    }
}

実行するとこんな感じの出力になる。

foobar {0 3 88} &[2 1 0]
foobaz {0 3 88} &[2 1 0]
barbaz {-1 -1 0} <nil>
barfoo {3 6 56} &[5 4 3]
bazfoo {3 6 56} &[5 4 3]

マッチすると res にはマッチした文字のインデックスの最初と最後とスコアが入る。スコアが高いと近似値が高い。これでソートするといい感じになる。

pos には pattern の文字がどことマッチしたのかのインデックスが入る。 foobar に対して foo を検索すると f が 0番目、1つ目の o が 1番目、 2つ目の o が2番目とマッチするので、 [0,1,2] となるがなぜか逆順で入っている。イミフ。

何しか、対象文字に対して、検索文字を太字にしたいとか思ったらこの pos を使うといい感じにできたりすると思う。それ以外の用途が思いつかない。

マッチしなかったら pos が になる。 res も -1 になるので、どちらでもマッチしなかったときの判定には使えそう。

成果物

そんなこんなで fzf-filterd を作った。

github.com

上記のマッチアルゴリズムを JSON-RPC から叩ける。

最終的には自作のランチャで使いたかったので汎用性には欠ける。自作のランチャに興味があるという稀有な人は以下にコードとバイナリがある。PR歓迎。

github.com

Raspberry pi 4 に iPhone をLightningケーブルでつなぐとFTP経由で中身がみれる仕組みを作ったはなし

このAI時代に個人ブログを残すことの意味はあるのかと思いつつ。色々苦労したことを内に留めておくよりかはこうやって吐き出すことでスッキリするという側面もあるかなと思ったので書いてみる。

どういうものを作ったのか?

iPhone を Lightningケーブルで Raspberry Pi 4 に接続すると FTP経由でパソコンから iPhone の中身が見れるシステムを作成しました。また、USBメモリの中身も Raspberry Pi 4 に挿すと FTP経由で見ることができます。

なぜ作ったのか?

とある現場では セキュリティの関係上、許可されていないデバイスをパソコンに接続することができないようになっており、パソコン間のデータのやり取りがネットワーク経由でなければならないが、ネットワークもセグメントが異なっているようなケースでは受け渡しが不便なのでローカルエリアに限った場合であればFTPサーバーのようなゲートウェイ機器を接続することでデータの受け渡しをスムーズにできるのではないか?という技術的興味があった。

仕組み

先に述べたとおりではありますが、iPhone を Raspberry Pi 4 に挿すと, /run/media/iphone に iPhone がマウントされます。 FTPサーバーは /run/media をルートディレクトリとしているので、FTPクライアントからは /iphone フォルダが見えます。画像データを見たければ /iphone/DCIM フォルダを見ればその中にあります。USBメモリもほとんど同じしくみで動作していて、USBメモリを挿すと /run/media/system の下に USBメモリのラベル名でフォルダが作成されます。

iPhoneの接続部分

iPhone は USBメモリのようにデフォルトではマウントできないので、ChatGPTに 「iPhone を USB でつないで、写真(DCIM内の画像)を自動転送 or 任意操作で転送できるようにするには」という問いを投げました。そうすると以下のように答えてくれました。

必要なソフトウェアスタック

  1. libimobiledevice Apple のプロトコル(usbmuxd, lockdown, afc, etc.)を扱う OSS ライブラリ。
  2. usbmuxd iOS デバイスと USB 越しに通信するためのデーモン。libimobiledevice の基盤。
  3. ifuse(任意) iPhone を FUSE を使ってマウントできる CLI ツール(内部に afc を利用)。
  4. udev + usbutils(任意) USB接続検知などに必要。

libmobiledevice については以下のリンクにソースがあります。 github.com

このあたり、 Yocto を構築してよしなにやってくれるようにしたので、いまいち仕組みがわかっていませんが私が作成した Yocto のレシピは以下のリンクのあたりに置いてあります

github.com ⇡これなんで必要だったかもはや覚えてないですが、これがないとおそらく iPhone を認識するデーモンが起動してくれないんだと思います。

github.com ⇡こっちが iPhone をマウントしている本体で、シェルスクリプトの中でやってます。 idevicepair というコマンドを使うと iPhone をペアリングするので、ペアリングがスタートしたら iPhone 側で許可してあげる必要があります。(この動作は Windows を使ってても同じ)

ラズパイを起動して、iPhone を挿すと「このコンピューターを信頼しますか?」と出るので「信頼」を選択して、iPhoneのパスコードを入力すると画面上の [iPhone Status] のところが mounted になり iPhone を認識したことを示します。その状態で FTPクライアントから 192.168.1.254 にアクセスすると iPhone の中身が見えます。あとは iphone/DCIM の下にあるファイルを取ってくれば、iPhoneからデータが吸い出せるという寸法です。

USBメモリ

github.com

USBメモリを自動でマウントするために、Linux の udev という仕組みを利用します。 /etc/udev/rules.d/ の下にルールファイルを置いておくと、そのルールに従ってUSBを認識したときにいろんなことを実行してくれます。今回は /dev/sd* ができたタイミング (USBメモリが機器に挿入されたタイミング) でサービスを起動するようにしていて、そのサービスの中で自動マウントを実行しています。もしかするとなにか抜けがあって認識できなかったりするかもですが、認識すると画面上の [USB storage status] のところにUSBのラベル名が表示されます。FTPクライアントで見たときも同様のラベル名で表示されます。

FTPサーバー

github.com

FTPサーバーは Go を使って書いています。と言っても go-ftpserver っていうまんまのパッケージがあってそれをビルドしただけです。 ftpserver.json に色々設定がかけて、匿名接続にも対応しているのでお手軽につなぎたいときは重宝しています。この go-ftpserver をサービス化して実行するだけのレシピが上のリンクのやつです。ここはそんなに説明することないかな?

LCD出力

LCDディスプレイは以下のものを使用しました。後で書くかもですがタッチパネルも搭載しているタイプです。あと表示はHDMIでつないで表示するタイプです。

https://www.amazon.co.jp/dp/B01N5HW3BP?ref=ppx_yo2ov_dt_b_fed_asin_title&th=1

つないでそのまま表示すると3.5インチなので文字が潰れて全然見れないので、480x320サイズまで解像度を落とす必要がありました。

local.conf に以下のように記述するといい感じに表示されるようになります。

github.com

それができたら、フレームバッファに描画したくなったわけなんですが、その前に画面上にログがびゃーっと出てきたり、ログインプロンプトが出たりするのがいやだったのでそれを無効化する設定を入れました。

↓こっちはログが出るのを抑える設定 github.com

↓こっちはログインプロンプトを抑制する設定。gettyサービスを無効化しています。 github.com

その後で、フレームバッファに画像だしたり、文字出したりするプログラムを作ってそれをサービス化して起動時に表示するようにしました。

github.com

タッチパネル

ここまでできると結構やりたいことはできてたんですが、電源をブチぎりしないと電源が切れないので、ちょっと嫌でした。というのも、ラズパイって電源ブチ切りするとSDカードを破損する可能性があるらしく、最悪起動しなくなるらしいんですよね。それを解決する方法としては、RAMディスク化して、その中で動作させる方法があるんですが、ちょっと私には敷居が高かったので、それだったら、タッチパネルで終了ボタン的なのを実装して、終了ボタンを押したら、 shutdown -h now コマンドを実行して終了させればいいやという発想になりました。

ただ、ボタン作って、そこが押されているかどうかを実装するのはそれはそれで手間だったので(LVGLとか入れてやろうと思ったんですがうまく行かず) 折衷案として、画面を5秒押し続けていたらシャットダウンするという仕組みをいれました。

github.com

これで、安心して電源を切れるようになりました。

まとめ

ラズパイを使って、iPhoneの写真データをFTP経由で吸い出すツールを作成しました。 Raspbian を使っても良かったんですが、組み込みっぽい作り方も学んでみたいと思ったので Yocto に入門して色々試しながら作ってみたのですが、案外すんなり作れたかなという印象でした。レシピという形で構成することで流用が効くため、次別のものを作成するときには参考にできるリポジトリができたかなと、自分の中では満足できる出来でした。

シリアル通信でRPC通信をするためのライブラリ Kuda を Go言語で作った

はじめに

この記事は私が作成したシリアル通信でRPCをするためのライブラリ Kuda についての紹介記事です。

github.com

経緯

シリアル通信でRPC通信できるのかという技術的興味に端を発したライブラリ。

RPCを使うようにした理由は、ファイル転送以外にも色々なデータをやり取りできるように作った方が今後の展開として面白そうだなと思ったのと、シリアル通信部分をユーザーにできるだけ意識させないように作りたかったからです。

使い方

シリアル通信で機器同士を接続してもらったうえで、以下のサーバーサイドのコード、クライアントサイドのコードをそれぞれの機器で実行します。

以下の例では、サーバー側が Linux、クライアント側が Windows となっています。

サーバー側コード

package main

import (
    "context"
    "net/http"

    "github.com/gorilla/rpc/v2"
    "github.com/gorilla/rpc/v2/json2"

    "kuda"
)

type (
    Calculator   struct{}
    AdditionArgs struct {
        Add, Added int
    }
    AdditionResult struct {
        Computation int
    }
)

func (c Calculator) Add(r *http.Request, args *AdditionArgs, result *AdditionResult) error {
    result.Computation = args.Add + args.Added
    return nil
}

func main() {
    s := rpc.NewServer()
    s.RegisterCodec(json2.NewCodec(), "application/json")
    calculator := &Calculator{}
    s.RegisterService(calculator, "")

    kuda.Serve("/dev/ttyGS0", s)
}

クライアント側コード

package main

import (
    "kuda"
    "log"
)

type (
    Calculator   struct{}
    AdditionArgs struct {
        Add, Added int
    }
    AdditionResult struct {
        Computation int
    }
)

func main() {
    client := kuda.Client{
        PortName: "COM9",
    }

    response, err := client.Call("Calculator.Add", &AdditionArgs{Added: 10, Add: 12})
    if err != nil {
        log.Fatalln(err)
    }
    var result AdditionResult
    err = response.GetObject(&result)
    if err != nil {
        log.Fatalln(err)
    }
    log.Printf("10 + 12 = %d", result.Computation)
}

見ていただいた通り、 gorilla/rpc で一度でも RPCしたことがある人であれば、結構すんなり読めるんじゃないでしょうか。

通常であれば、 http のサーバーライブラリを使いますが、その部分を kuda で置き換えしています。

これができるのも、 net/http の抽象化が強力だからですね。 Go言語の素晴らしいところを享受させてもらいました。

内部実装

gorilla/rpc をシリアル通信で接続するために

zenn.dev

こちらの例にある通り、 gorilla/rpc の RPCサーバーを http.Handle() に http.Handler として渡すことで http サーバーを経由して要求が RPCサーバーに渡ってくるようになります。なので、httpサーバーがやっていることを疑似的に再現してあげればシリアル通信でもRPC通信ができるようになるはずです。

http.ListenAndServe() でサーバーが起動します。HTTPで要求がきたら、内部でごにょごにょして、http.Handler() で登録した http.Handler インターフェースの ServeHTTP() を呼び出します。要は、http.Handler の インターフェースの ServeHTTP() を呼び出せれば RPC通信ができるようになります。 ListenAndServe() の詳しい処理の内容は以下のブログが詳しいかと思います。ご興味のある方はこちらを参考にされると良いかと思います。このブログでは本筋からそれるのでこれ以上は扱いません。

zenn.dev

ServerHTTPの定義は以下のようになっています。第一引数に http.ResponseWriter, 第二引数に *http.Request を取ります。

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

http.ResponseWriter はインターフェースで 以下のメソッドを実装していなければなりません。今回はこちらを自作する必要がありました。

type ResponseWriter interface {
    Header() http.Header

    Write([]byte) (int, error)

    WriteHeader(statusCode int)
}

Header() http.Header および WriteHeader(statusCode int) は HTTPのヘッダー生成に関連する箇所で今回は使用しませんが、 Header() メソッドで http.Header を返してあげないと gorilla/rpc でエラーとなります。 http.Header は map[string][]string のエイリアスなので、空のmapを返すことで対処します。本題は Write([]byte) (int, error) ですが、渡されるバイト列は json なので それをそのままシリアル回線に流すようにすればOKです。そういう考えで作った自作 ResponseWriter が以下の通り。構造体のメンバーの writer がシリアル通信を担う構造体です。

type response struct {
    writer io.Writer
}

func (r *response) Header() http.Header {
    header := make(map[string][]string, 0)
    return header
}

func (r *response) Write(data []byte) (int, error) {
    n, err := r.writer.Write(data)
    if err != nil {
        r.err = err
    }
    return n, nil
}

func (r *response) WriteHeader(statusCode int) {
}

Henader インターフェースの定義の ServerHTTP の引数に戻って、次は第二引数の http.Request を作成します。こちらは、http.NewRequest() で作成することができます。渡す引数は、第一引数に HTTPメソッド、第二引数に URL、第三引数に body を表す io.Reader インターフェースを実装した構造体となります。 gorilla/rpc の ServeHTTP は HTTPメソッドが POST でないと受け付けてくれません。URLはシリアル通信では不要なので空文字とし、io.Reader には bytes.Buffer で受信したバイト列を包んで渡します。

body := &bytes.Buffer{}
/* この間で シリアル通信からデータを受信して body に詰める */
request, _ := http.NewRequest("POST", "", body)
/* ここで responseWriter を作成する */
handler.ServeHTTP(responseWriter, request)

シリアル通信ライブラリ

github.com

シリアル通信ライブラリは bugst/go-serial を使用しています。

ポートオープンの仕方はこんな感じでいけます。

mode := &serial.Mode{
    BaudRate: 115200,
}
port, err := serial.Open("/dev/ttyUSB0", mode)
if err != nil {
    log.Fatal(err)
}

Read / Write はこんな感じ

n, err := port.Write([]byte("10,20,30\n\r"))
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Sent %v bytes\n", n)

buff := make([]byte, 100)
for {
    n, err := port.Read(buff)
    if err != nil {
        log.Fatal(err)
        break
    }
    if n == 0 {
        fmt.Println("\nEOF")
        break
    }
    fmt.Printf("%v", string(buff[:n]))
}

これを Kuda ライブラリの中で使用しています。

通信プロトコル

単純にシリアルライブラリとサーバー部分をくっつけるだけでも動くんですがデータ量が多い場合、バッファサイズを超えてしまって正しく送受信できません。なので、データ量が多い場合は分割して送信するようにしてます。分割サイズは 1024 バイトにしてます。次のデータを送信するかどうかは ACK が受信できたかどうかで判別します。

送受信の方法

これだと受信側で終わりがわからないので、データフォーマットを以下のようにしています。

データフォーマット

ACK応答も同じフォーマットで送信しています。

ACK応答

もしかすると、エラー応答用に Status フィールドをヘッダー部分に追加することでより汎用性のあるプロトコルになるかもですが、今のところはシンプル設計にしてます。

まとめ

シリアル通信で RPC通信 をするための 自作ライブラリ Kuda についての紹介記事を書きました。

みなさんも、これを機にシリアル通信で RPCしてみてもいいかもしれませんし

トランスポート層を挿げ替えていろんなものと通信させてみても面白いかもしれませんね。

ImageMagickをビルドして画像比較APIをCのソースから呼ぶまで

ImageMagick の準備

GitHub - ImageMagick/ImageMagick-Windows: Windows build of ImageMagick 7

上記のリポジトリを取ってくる。

README に依存関係とかちゃんと書いてくれてるのでその通りに Visual Studio をインストール。

CloneRepositories.IM7.cmd を実行

Configure.sln を開いてビルド。 Configure.exe っていうのが Configure フォルダにできる。

Configure.exe を実行すると、ImageMagickのソリューションを設定するためのツールが立ち上がる。自分の環境に合わせて設定する。私の場合は OpenCLは不要だったのと、静的ライブラリが欲しかったのでそちらにチェックした。

実行すると、リポジトリのルートにソリューションファイルが出来上がる。

ソリューションを開いてビルドすると、 Artifacts/lib に静的ライブラリができる。

利用側ソースコード

何でもいいので、VIsual Studio で C++ のソリューションを作成。

ソリューションのプロパティを開いて

「C/C++」→「全般」→「追加のインクルードディレクトリ」に ImageMagick のリポジトリの「ImageMagick」ディレクトリを指定。

また、「リンカー」→「入力」→「追加の依存ファイル」に先ほどビルドした *.lib ファイルを全部突っ込む。

#include <stdio.h>
#include <MagickWand/MagickWand.h>

int main(int argc, char** argv)
{
    // ImageMagick環境の初期化
    MagickWandGenesis();

    // MagickWandの作成
    MagickWand* image1_wand = NULL;
    MagickWand* image2_wand = NULL;
    MagickWand* diff_wand = NULL;
    double distortion;

    // 画像を処理するためのMagickWandを作成
    image1_wand = NewMagickWand();
    image2_wand = NewMagickWand();
    diff_wand = NewMagickWand();

    // 入力画像を読み込む
    if (MagickReadImage(image1_wand, "C:\\Users\\bamch\\Downloads\\expected.png") == MagickFalse) {
        fprintf(stderr, "画像1の読み込みに失敗しました\n");
        return 1;
    }

    if (MagickReadImage(image2_wand, "C:\\Users\\bamch\\Downloads\\compare.png") == MagickFalse) {
        fprintf(stderr, "画像2の読み込みに失敗しました\n");
        return 1;
    }

    MagickSetImageFuzz(image1_wand, 1.0);
    MagickSetImageFuzz(image2_wand, 1.0);

    diff_wand = MagickCompareImages(image1_wand, image2_wand, AbsoluteErrorMetric, &distortion);

    // 画像比較を行い、差分を取得
    if (diff_wand == 0) {
        fprintf(stderr, "画像の比較に失敗しました\n");
        return 1;
    }

    // 差分を保存する
    if (MagickWriteImage(diff_wand, "C:\\Users\\bamch\\Downloads\\difference.png") == MagickFalse) {
        fprintf(stderr, "差分画像の保存に失敗しました\n");
        return 1;
    }

    // 画像間の歪み(類似度)を出力
    printf("画像間の歪み: %f\n", distortion);

    // リソースの解放
    image1_wand = DestroyMagickWand(image1_wand);
    image2_wand = DestroyMagickWand(image2_wand);
    diff_wand = DestroyMagickWand(diff_wand);

    // ImageMagick環境のクリーンアップ
    MagickWandTerminus();

    printf("画像比較が完了しました\n");
    return 0;
}

こんな感じでソースをかいてビルドしたら、完了。

Magick++ の場合

Magick++ の場合のコードはこんな感じ。

加えて、インクルードディレクトリに ImageMagickのリポジトリの ImageMagick\Magick++\lib を足すこと。

#include <Magick++.h>
#include <iostream>

using namespace Magick;
using namespace std;

int main(int argc, char** argv)
{
    // ImageMagick環境の初期化
    InitializeMagick(*argv);

    // 入力画像をロード
    Image image1;
    Image image2;
    try {
        image1.read("expected.png");
        image2.read("compare.png");
    }
    catch (Exception& error) {
        cout << "画像の読み込みに失敗しました: " << error.what() << endl;
        return 1;
    }

    // ファジネスを設定 (色の微小な違いを無視)
    double fuzz_percentage = 10.0; // 10%のファジネス
    image1.colorFuzz(fuzz_percentage * QuantumRange / 100.0);
    image2.colorFuzz(fuzz_percentage * QuantumRange / 100.0);

    // 歪みのメトリック (類似度) を計算し、差分画像を作成
    double distortion = 0.0;
    Image diff_image;
    try {
        diff_image = image1.compare(image2, AbsoluteErrorMetric, &distortion);
        diff_image.write("difference.png");
    }
    catch (Exception& error) {
        cout << "画像の比較に失敗しました: " << error.what() << endl;
        return 1;
    }

    // 歪み度を出力
    cout << "画像間の歪み (類似度): " << distortion << endl;
    cout << "差分画像を 'difference.png' に保存しました。" << endl;

    return 0;
}