Help us understand the problem. What is going on with this article?

AndroidでBluetooth関係のAPIを使ってPAPERANGに接続してみる その2

はじめに

前回から進捗があったので。

コードはGitHubで公開。
ライセンス周りがちょっと怪しいところがあるのでその辺はもう少し調べて解決したい。
GitHubはこちら⇒paperangsample

今回は画像の印刷で苦労したのでその辺を・・・

P_20200225_002555_vHDR_Auto.jpg

画像の印刷について -前置き-

PAPERANG P1で扱う画像は、横384ピクセルになります。
画像は2値化して、1ピクセルを1ビットに変換します。
なので、384/8=48byteで横を表現します。
一度に印刷できる高さは41ピクセルのようです。
なので、一度に印刷できるデータ量は48bytex41=1968byteとなります。
このサイズを超える画像は、分割して処理する必要があります。

ビットで表現することになるので、当然濃淡はつけられません。
そのため、誤差分散法などを用いて、画像の濃淡を表現する必要があります。
今回のプログラムでは以下の記事を参考にさせていただきました。
【OpenCV】3つの擬似濃淡変換(ディザリング)の解説【Python】

画像の印刷について

今回はグレースケールに変換後、誤差拡散法を用いて変換しています。
この辺、OpenCVを使うと楽にできそうなんですが、なんとなく使いたくなかったので、Canvasを使って、グレースけるにしています。
グレースケールにする際に、元画像のサイズも調整しています。

グレースケールの処理は以下の通りです。

    public Bitmap convGrayscale(Bitmap img){
        int width = img.getWidth();
        int height = img.getHeight();
        if(width != 384) {
            // サイズ調整
            float par = 384f / (float) width;
            Log.i("PAPERANG", Float.toString(par));
            width = Math.round(width * par);
            height = Math.round(height * par);
            if (width != 384) {
                width = 384;
            }
        }
        Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
        Canvas canvas = new Canvas(bmp);
        Paint paint = new Paint();

        ColorMatrix cm = new ColorMatrix();
        cm.setSaturation(0);
        ColorMatrixColorFilter f = new ColorMatrixColorFilter(cm);
        paint.setColorFilter(f);

        Rect distRect = new Rect(0, 0, width, height);
        Rect srcRect = new Rect(0, 0, img.getWidth(), img.getHeight());
        canvas.drawBitmap(img, srcRect, distRect, paint);
        canvas = null;
        return bmp;
    }

Canvasにフィルタを適用していますが、ColorMatrix.setSaturation()メソッドで引数に0を指定し、ColorMatrixFilterに設定することで、グレースケールのフィルタになるとのことで、そのように処理しています。
あとは、元画像をグレースケールのフィルタを適用したキャンバスに描画します。
これで、グレースケールの画像が完成します。

次に、誤差拡散法で2値化します。
メソッド名がダサいですが、処理は以下の通りです。

    public Bitmap conv2Value(Bitmap img){
        Bitmap bmp = convGrayscale(img);
        int width = bmp.getWidth();
        int height = bmp.getHeight();
        int thresh = 128;
        int err = 0;

        int[] gray= new int[width * height];
        bmp.getPixels(gray, 0, width, 0, 0, width, height);

        for(int y = 0; y < height; y++){
            for(int x = 0; x < width; x++){
                int color = gray[y * width + x];
                int r = Color.red(color);
                //int g = Color.green(color);
                //int b = Color.blue(color);

                if(r + err < thresh){
                    err = r + err - 0;
                    gray[y * width + x] = Color.argb(255,0,0,0);
                }else{
                    err = r + err - 255;
                    gray[y * width + x] = Color.argb(255,255,255,255);
                }
            }
        }

        bmp.setPixels(gray, 0, width, 0, 0, width, height);
        return bmp;
    }

引数の画像のピクセルの配列を取得します。
取得したピクセルからR(赤)の値を取得します。
これは、グレースケールにしているので、RGBのどれでもいいので、Rにしています。
そして、誤差拡散法で2値化していきます。
(誤差拡散法についてはどこかでちゃんと理解せねば・・・)
閾値を超えるか超えないかで白か黒かを決めていきます。
参考サイトを見てもらいとわかりますが、今回の方法からさらに進んだ方法もあります。

できたデータをビットマップを作成します。
Bitmapオブジェクトにすることで、画面に表示して確認することも可能です。

印刷コマンドを発行する部分ですが、以下のようなソースになっています。

    public void printImage(Bitmap img) throws IOException {
        int width = img.getWidth();
        int height = img.getHeight();
        int[] pixcels = new int[width * height];
        img.getPixels(pixcels, 0, width, 0, 0, width, height);
        byte[] bits = new byte[48 * height];
        for(int y = 0; y < height; y++){
            for(int x = 0; x < width; x++){
                int r = Color.red(pixcels[y * width + x]);
                if(r == 0x00) {
                    int idx = y * 48 + (x / 8);
                    switch(x % 8){
                        case 0:
                            bits[idx] = (byte)(bits[idx] | (byte)0x80);
                            break;
                        case 1:
                            bits[idx] = (byte)(bits[idx] | (byte)0x40);
                            break;
                        case 2:
                            bits[idx] = (byte)(bits[idx] | (byte)0x20);
                            break;
                        case 3:
                            bits[idx] = (byte)(bits[idx] | (byte)0x10);
                            break;
                        case 4:
                            bits[idx] = (byte)(bits[idx] | (byte)0x08);
                            break;
                        case 5:
                            bits[idx] = (byte)(bits[idx] | (byte)0x04);
                            break;
                        case 6:
                            bits[idx] = (byte)(bits[idx] | (byte)0x02);
                            break;
                        case 7:
                            bits[idx] = (byte)(bits[idx] | (byte)0x01);
                            break;
                    }
                }
            }
        }
        int loffset = 41;
        if(height <= 41){
            loffset = height;
        }
        try {
            byte[] buff = new byte[48];
            ByteBuffer buffs = ByteBuffer.allocate(48 * loffset);
            buffs.order(ByteOrder.LITTLE_ENDIAN);
            for (int i = 0; i < bits.length; i++) {
                if(i % (48 * loffset) == 0 && i > 0){
                    //ResultData ret = getStatus();
                    Log.i("PAPERANG","print");
                    printData(buffs.array());
                    buffs.clear();
                    buffs = ByteBuffer.allocate(48 * loffset);
                    buffs.order(ByteOrder.LITTLE_ENDIAN);
                }
                buffs.put(bits[i]);
            }
            printData(buffs.array());
        }catch(Exception e){
            if(e != null){
                if(e.getMessage() != null){
                    Log.e("PAPERANG", e.getMessage());
                }else{
                    Log.e("PAPERANG", e.toString());
                }
            }
        }
    }

最初の方で、画像のピクセル情報を取ってきて、ビットに変換しています。
実際に印刷して気づいた点

  • 1は白、0は黒で印刷しないといけません。
  • 1バイト目は8ビット、2バイト目は7ビット・・・というように変換が必要

これら2点に気を付けないと、プレビューで誤差拡散した画像はOK、印刷するとそれっぽいのが出てるんだけどなんか違うということになります。
ちなみに、switch文でやるとダサい感じになるけど、面倒なのでw

ビットに変換して、配列に入れたあと、最後のfor文で印刷を実施します。
ここでは、48byte x 41 で印刷するように処理しています。

最後に

おもちゃとしてはなかなか楽しめました。
今後はまだ未確認のコマンドを確認していきたいと思います。
変数名やメソッド名がダサい部分があるので、どこかで見直しておきたいところ。
C#版とか作りたいような気もしてるけど作らないかもw
(公開したコード読んでくれれば多分簡単にできるんじゃないかな?)
画像が出るようになったので、オープンソースカンファレンス 2020 Hamanakoでなにか展示するかもです。

krohigewagma
お仕事ではJava、C#、PostgreSQLなんかを触ってます。 趣味ではAndroidアプリ開発やお絵描き、作曲してます。 過去にはx68kアセンブラやDelphi、VC++なんかもやってました。 最近、知識の時代遅れ感に焦りをちょっと覚えています^^; 働き方も考えないとなぁ・・・
http://taka-hama.sakura.ne.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした