読者です 読者をやめる 読者になる 読者になる

Androidアプリで1次元バーコードを作成する方法2

前回は「ZXing」ライブラリを使用して、Androidアプリで1次元バーコードを作成・表示する方法を紹介した。
今回はライブラリを使用せず、全て自前で1次元バーコードを作成・表示する方法を紹介する。

今回も1次元バーコード規格は「CODABAR(NW-7)」に限定するものとする。

<前置き>

「ZXing」は優れたバーコード作成・読取り用のライブラリである。
Androidアプリで1次元バーコードを作成・表示する必要があり、「ZXing」が使用できるのであれば、迷わず「ZXing」を使用する事をお勧めする。
今回紹介するライブラリを使用せず、1次元バーコードを表示する方法は、何らかの事情で「ZXing」が使用できない場合の方法と考えて頂きたい。

参考までに筆者が「ZXing」を使用できなかった事情を簡単に紹介する。
・アプリのapkファイルサイズを1KBでも削減する必要があった
・バーコードに一部独自の仕様を実装する必要があった

<ソースコード(ライブラリの代替側)>

「CODABAR(NW-7)」規格の1次元バーコードを作成するために、「ZXing」ライブラリの代替として、「MyCodaBarWriter」と「MyBitMatrix」の2クラスを作成した。

MyCodaBarWriter.java

package biz.accele.samplebarcode;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MyCodaBarWriter {

    //スタートキャラクタ、ストップキャラクタ(A,B,C,Dの中から選択する)
    private final String START_CHAR = "A";
    private final String END_CHAAR  = "A";

    //バーコードの両端に設けるマージンサイズ(クワイエットゾーン)を棒幅の何個分かで指定する
    private final int SIDE_MARGIN_NUM = 10;

    //エレメントの値
    private final int WHITE = 0;     //白エレメント
    private final int BLACK = 1;     //黒エレメント

    //変換用情報(CODABARの仕様より作成)
    private final int[] convert0 = {BLACK, WHITE, BLACK, WHITE, BLACK, WHITE, WHITE, BLACK, BLACK};
    private final int[] convert1 = {BLACK, WHITE, BLACK, WHITE, BLACK, BLACK, WHITE, WHITE, BLACK};
    private final int[] convert2 = {BLACK, WHITE, BLACK, WHITE, WHITE, BLACK, WHITE, BLACK, BLACK};
    private final int[] convert3 = {BLACK, BLACK, WHITE, WHITE, BLACK, WHITE, BLACK, WHITE, BLACK};
    private final int[] convert4 = {BLACK, WHITE, BLACK, BLACK, WHITE, BLACK, WHITE, WHITE, BLACK};
    private final int[] convert5 = {BLACK, BLACK, WHITE, BLACK, WHITE, BLACK, WHITE, WHITE, BLACK};
    private final int[] convert6 = {BLACK, WHITE, WHITE, BLACK, WHITE, BLACK, WHITE, BLACK, BLACK};
    private final int[] convert7 = {BLACK, WHITE, WHITE, BLACK, WHITE, BLACK, BLACK, WHITE, BLACK};
    private final int[] convert8 = {BLACK, WHITE, WHITE, BLACK, BLACK, WHITE, BLACK, WHITE, BLACK};
    private final int[] convert9 = {BLACK, BLACK, WHITE, BLACK, WHITE, WHITE, BLACK, WHITE, BLACK};
    private final int[] convertA = {BLACK, WHITE, BLACK, BLACK, WHITE, WHITE, BLACK, WHITE, WHITE, BLACK};
    private final int[] convertB = {BLACK, WHITE, WHITE, BLACK, WHITE, WHITE, BLACK, WHITE, BLACK, BLACK};
    private final int[] convertC = {BLACK, WHITE, BLACK, WHITE, WHITE, BLACK, WHITE, WHITE, BLACK, BLACK};
    private final int[] convertD = {BLACK, WHITE, BLACK, WHITE, WHITE, BLACK, BLACK, WHITE, WHITE, BLACK};

    //変換用情報をMap型に格納する
    private final Map<String, Object> mapConvert = new HashMap<String, Object>(){
        {
            put("0", convert0);
            put("1", convert1);
            put("2", convert2);
            put("3", convert3);
            put("4", convert4);
            put("5", convert5);
            put("6", convert6);
            put("7", convert7);
            put("8", convert8);
            put("9", convert9);
            put("A", convertA);
            put("B", convertB);
            put("C", convertC);
            put("D", convertD);
        }
    };


    public MyBitMatrix encode(String targetData, int width, int height){

        //スタートキャラクタ、ストップキャラクタの付与
        String exTargetData = START_CHAR + targetData + END_CHAAR;

        //データの変換
        List<Integer> codeArray = convertData(exTargetData);

        //バーコードデータの生成
        MyBitMatrix bitMatrix = createBarcode(codeArray, width, height);

        return bitMatrix;
    }

    // データを変換する
    private List<Integer> convertData(String targetData){

        List<Integer> resultArray = new ArrayList<>();

        //サイドマージン(左)
        for(int i=0; i<SIDE_MARGIN_NUM; i++){
            resultArray.add(WHITE);
        }

        //データ変換
        for(int i=0; i<targetData.length(); i++){

            // 先頭から1文字づつ取り出す
            String targetChar = targetData.substring(i, i+1);

            // 取り出した文字を変換する
            joinArray(resultArray, targetChar);

            // キャラクターギャップ
            resultArray.add(WHITE);
        }

        // 最後のキャラクターギャップを削除する
        resultArray.remove(resultArray.size() - 1);

        //サイドマージン(右)
        for(int i=0; i<SIDE_MARGIN_NUM; i++){
            resultArray.add(WHITE);
        }

        return resultArray;
    }

    //Listの末尾に文字を変換した情報を付け加える
    private void joinArray(List<Integer> resultArray, String targetChar){

        int[] targetArray = (int[])mapConvert.get(targetChar);

        for(int i=0; i<targetArray.length; i++){

            resultArray.add(targetArray[i]);
        }
    }

    //バーコード情報を生成する
    private MyBitMatrix createBarcode(List<Integer> targetData, int width, int height) {

        // バー幅の算出
        int multiple = getBarWidth(targetData.size(), width);

        // バーコード描画サイズの算出
        int drawPx = targetData.size() * multiple;

        // 要求幅に合わせるために必要な調整幅を算出する
        int leftSideAdjust  = (width - drawPx) / 2;

        // 結果を格納するためのBitMatrixをインスタンス化する
        MyBitMatrix output = new MyBitMatrix(width, height);

        // 制御変数の初期化
        int outputX = leftSideAdjust;

        for (int inputX = 0; inputX < targetData.size(); inputX++) {

            if (targetData.get(inputX).intValue() == BLACK) {

                output.setRegion(outputX, 0, multiple, height);
                outputX += multiple;

            }else{

                outputX += multiple;
            }
        }
        return output;
    }

    // 指定されたピクセル数を超えず、かつ最大限大きく描画するためにバー幅の最適値を算出する
    private int getBarWidth(int barCount, int viewWidth){

        int resultMultiple = 1;

        int multiple = 1;

        while(true){

            int px = barCount * multiple;

            if(px > viewWidth){

                break;

            }else{

                resultMultiple = multiple;
            }

            multiple += 1;
        }

        return resultMultiple;
    }
}

MyBitMatrix.java

package biz.accele.samplebarcode;

public final class MyBitMatrix {

    private final int rowSize;
    private final int[] bits;

    public MyBitMatrix(int width, int height) {

        this.rowSize = (width + 31) / 32;
        bits = new int[rowSize * height];
    }

    public boolean get(int x, int y) {
        int offset = y * rowSize + (x / 32);
        return ((bits[offset] >>> (x & 0x1f)) & 1) != 0;
    }

    public void setRegion(int left, int top, int width, int height) {

        int right = left + width;
        int bottom = top + height;

        for (int y = top; y < bottom; y++) {
            int offset = y * rowSize;
            for (int x = left; x < right; x++) {
                bits[offset + (x / 32)] |= 1 << (x & 0x1f);
            }
        }
    }
}

<ソースコード(呼び出し側)>

前回の「ZXing」ライブラリを使用する場合と比較して、変更点は多くない。
(activity_main.xmlに関しては完全に同一である)

MainActivity.java

package biz.accele.samplebarcode;

import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Matrix;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ImageView;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ImageView imageView = (ImageView) findViewById(R.id.result_view);

        // バーコードの各種設定
        String        targetData = "123456789012345";       //バーコードに変換する対象データ
        int           width      = 1000;                    //作成するバーコードの幅
        int           height     = 200;                     //作成するバーコードの高さ

        // データ変換用クラスをインスタンス化する
        // 変更点1
        // ・CodaBarWriter -> MyCodaBarWriter
        MyCodaBarWriter writer = new MyCodaBarWriter();


        try {

            // 対象データを変換する
            // 変更点2
            // ・BitMatrix -> MyBitMatrix
            // ・引数の数が少なくなった
            MyBitMatrix bitMatrix = writer.encode(targetData, width, height);


            // BitMatrixのデータが「true」の時は「黒」を設定し、「false」の時は「白」を設定する
            int[] pixels = new int[width * height];
            for (int y = 0; y < height; y++) {
                int offset = y * width;
                for (int x = 0; x < width; x++) {
                    pixels[offset + x] = bitMatrix.get(x, y) ? Color.BLACK : Color.WHITE;
                }
            }

            // ビットマップ形式に変換する
            Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
            bitmap.setPixels(pixels, 0, width, 0, 0, width, height);

            //ビットマップの回転(縦に表示するため)
            Matrix mat = new Matrix();
            mat.postRotate(90);
            Bitmap bmp = Bitmap.createBitmap(bitmap, 0, 0, width, height, mat, true);

            // イメージビューに表示する
            imageView.setImageBitmap(bmp);

        } catch (Exception e) {}
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#F0E68C"
    tools:context="biz.accele.samplebarcode.MainActivity">

    <!-- バーコードを表示するためのImageView -->
    <ImageView
        android:id="@+id/result_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true"
        />

</RelativeLayout>

<実行結果>

f:id:MoonMtLab:20160918094958p:plain:w300

Enjoy Programing!!