JW_cad Viewer、JWCファイルに対応

JW_cad Viewerを作った時からJWCファイルに対応するか悩んでいました。いまさらDOSの時代のファイル対応に時間をかけるべきなのか?需要はあるのか?

しかし、JW_CAD Viewerというアプリ名にしてしまったのだから、やっぱり対応しようと思い立ちました。需要は無いかもしれませんが、私個人の興味もあったので。そして、やってみたら意外と簡単に読めました。

この調子だと、DXFも対応するかもしれない・・・。

ブレークポイントに斜線

先日まで普通にデバッグできていたのに、突然ブレークポイントに斜線が出てブレークポイントで止まらなくなった(無効状態?)。特に何かやった記憶はない

斜線の入ったブレークポイントの上にマウスカーソルを置くと、”breakpoint does not belong to any class”というメッセージが表示された。意味が分からない。他のプロジェクトも試したが、同じ状態になる。

検索をかけて、最終的にここから、FileメニューのInvalidate Caches/Restartを実行すると、ブレークポイントが正常になった。

そういえば、AndroidStudioが起動途中で止まったことがあったなぁと思いだした。その後は正常にコンパイルと普通の実行はできていたので気にしてなかった。

JW_cad Viewer、SXF寸法に対応(仮)

JWW形式のSXF寸法図形の仕様を見て、素直に思うままに JW_cad Viewer に実装してみた。普通の寸法線と半径は表示できた。ただ、SXF寸法を使った図面が手元にあまりなくて、十分確認できていない(オプションの意味がまだ分からない)。

はまったところは、背景が白で線色も白だったので、何も表示されなかったところ。JW_cadのSXF読み込みオプションに「背景色と同じ色を反転する」とあるが、そういうことかと納得。このオプションはJWWファイルに保存されていないようなので、別途設定を設けた。もしかしたら、探し方が悪いだけで、このオプションはJWWファイルに記録されているかもしれない。あったほうがファイル受け渡しでトラブルが少なくなるので。

JW_cad Viewer公開したよ

Androidアプリを公開しました。JW_cadというWindows用のCADのViewerで、JW_cad Viewerと言います。そのままですね。製作には、主に休日を使い、約一ヶ月かかりました。

作った理由は、

  • グラフィック関係のアプリが作りたかった。
  • CADみたいな線画が好き。
  • DXFは、すでにライブラリがあるので面白みがない。
  • DWGは、有料のライブラリがないとつらい。そもそもAutoCADが手元にない。
  • JW_cadのデータ構造は、ありがたいことに公開されている。ユーザーも日本では多い。
  • JW_cadのデータ構造を理解すると、他のプログラム作成に応用が利く。
  • JW_cadの は、保存形式としてMFCのシリアライズを利用している。MFC以外の環境で、MFCでシリアライズされたオブジェクトを読むことに興味があった。

というような感じでJW_cadのViewerを作ることにしました。一番の理由はMFCのシリアライズに対する興味です。

アプリの作成は、苦労しました。苦労した点は、

  • データ形式の仕様が、保存部分のソースコードの抜粋で、わかりずらい(と思うけど、かえって文章でかいてあるよりいいのかな?。
  • C++のunsignedな数値が、いたるところに使ってある(ということが問題になるとは予想外だった ・ ・ ・ )。
  • JW_cad、変数使いまわし多すぎ問題(その割に、ベースクラスの使わないメンバが多い。継承されるクラスは少ないから、ベースクラスをもっとコンパクトにすればよかったのに)。
  • ソリッド図形の謎。
  • 文字クラスが画像という不思議(JW_cadを使っていれば既知なんだけど、あらためて疑問)。

作成方法としては、まずWindows上でバイナリデータ解析をバイナリエディタでしつつ、C#でプロトタイプ作りをしました。C#でプロトタイピングしたので、Xamarinでアプリを作ろうかと少し思いました。ですが、Xamarinは開発環境の動作が重いので苦労すると思い、C#からkotlinに変換しました。

MFCでシリアライズされたオブジェクトの読み込みは、こここことマイクロソフトのテクニカルノートが参考になりました。オブジェクト(ポインタ?)の読み込みは、テクニカルノートを読んで、バイナリの解析(オブジェクトのIDが、想像していたものとちょっと違って悩んだ)。

MFCのシリアライズについては、面白いことが分かったので、また何か書くかもしれません。

Amazfit Bip ボタンコントローラ

アプリを公開しました。

Pebbleというスマートウォッチを以前使っていたのですが、開発元がなくなって、次に購入したのがAmazfit Bipでした。これが結構すごくて、通知は最初からちゃんと日本語で出るし、電池の持ちもいいし、止まることなくちゃんと動いています。いや、Pebbleが不安定だったので・・・。

欠点は

  • A-GPSやFirmwareなどの更新処理が長く、いつの間にか始まって、その間何もできない
  • Pebbleのようにアプリで拡張できない。
  • Watchfaceが作れるが、Pebbleほど自由度がない。

など、特にPebbleの特徴のアプリで拡張できないのが残念。調べると、Tools & Amazfitというのを入れると、Taskerと連携してボタンでスマホの機能を使えることを知りました。でも、自分の環境で通知がうまく表示されないという問題があって使えませんでした。

しかし、ボタンが使えるとわかれば、自分で作ればいいということで、いろいろ調べてアプリを作りました。

ボタンは長押しとクリックでモールス信号をと思ったのですが、長押しの認識時間が時計側で変更できないので、いまいち上手くいかなかったので、それほどコマンドパターンが増やせなかったのが残念。誤動作が増えてもいいならモールス化できるのですが、実際かなり誤動作があってあきらめました。まぁ、そもそもそんなに機能がいるとも思えないですし。

そういえば、ボタン以外にも画面タッチも認識できるみたいですが、いまひとつ条件がわからなかったです。自由に使えるわけではないみたいです。

AndroidでのBluetooth LEとServiceの知識を(限定的ですが)手に入れたのが今回の成果かな?

JCodecでh.264ファイルからAndroidのBitmapを取得(任意のフレーム)

JCodecを使って任意のフレームを取得してみます。勘ですぐに気づくと思いますが、nextFrame()をひたすら読みだせばできそうです。 nextFrame() が返すオブジェクトのメンバーに frameNoというメンバもあります。

    //ダメな例
    fun getFrame(pos : Long) : Bitmap{
        val file = File("/sdcard/000.h264")
        val buf = NIOUtils.fetchFromFile(file)
        val es = BufferH264ES(buf)
        var nextFrame  =  es.nextFrame()
        val decoder = H264Decoder()
        val pic = Picture.create(1280, 1024, ColorSpace.YUV420)
        while(nextFrame != null && nextFrame.frameNo != pos){
            nextFrame  =  es.nextFrame()
        }
        val op = decoder.decodeFrame(nextFrame.data, pic.data).cropped()
        return AndroidUtil.toBitmap(op)
    }

これで指定したフレームのビットマップが得られそうに思うじゃないですか。ダメです。h.264は前フレームとの (正確な言い方じゃないけど) 差分を使います。なので、フレームごとにデコードしなければだめです。

    fun getFrame(pos : Long) : Bitmap{
        val file = File("/sdcard/000.h264")
        val buf = NIOUtils.fetchFromFile(file)
        val es = BufferH264ES(buf)
        var nextFrame  =  es.nextFrame()
        val decoder = H264Decoder()
        val pic = Picture.create(1280, 1024, ColorSpace.YUV420)
        while(nextFrame != null && nextFrame.frameNo != pos){
            val op = decoder.decodeFrame(nextFrame.data, pic.data)
            nextFrame  =  es.nextFrame()
        }
        val op = decoder.decodeFrame(nextFrame.data, pic.data).cropped()
        return AndroidUtil.toBitmap(op)
    }

これで取得できるのですが、終わりのほうのフレームが欲しい時に、これでは効率が悪いですよね?ということで、次に続く。

DSGViewer

またAndroidのアプリを作りました。 DSGViewerです。日立の監視用デジタルレコーダーDS-Gシリーズ及びDS-JHシリーズの録画ファイルを見るためのアプリです。
Windows向けのビューワはあるのですが、Androidでも見れたら便利かなと。

たまたま(?)録画ファイルが手元にあり、調べたらフォーマットはそれほど難しい構造ではないので作りました。jpeg2000とかh.264とかは扱いに困りましたが。

ただ、万人向けでないので、需要はないと思います。

JPEG2000読み込み

AndroidでJPEG2000を読み込む必要があったので調べた結果、jj2000を使うことにしました。しかし、JJ2000ライブラリは、いろいろな派生物があってどれを使っていいか悩みました。

自分は、Androidで使えるBitmapが取り出せるもので、
NFC_DriversLicenseReader のJJ2000を使うことにしました。jj2000自体はjj2000ライセンス(?)ですが、NFC_DriverLicenseReaderはMIT License です。

まず、 ダウンロードしてjj2000フォルダをプロジェクトにコピーします。私は、以下の場所にコピーしました。

ただ、このまま使うとBitmapが白黒になるので、一部修正します。
jj2000/j2k/decoder/Decoder.javaの378行のコメントを外し、次の行をコメントアウトします

      ImgWriterBitmap imwriter = new ImgWriterBitmapPPM(decodedImage,0,1,2);
//      ImgWriterBitmap imwriter = new ImgWriterBitmapPPM(decodedImage,0,0,0);	//モノクロ対応 2012.08.06 TK

あとは、

    val buffer = File("/sdcard/test.jp2").readBytes()
    val bmp = JJ2000Frontend.decode(buffer)

これでBitmapが得られます。参考に、MainActivityを次のように作って確認しました。

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import jj2000.JJ2000Frontend
import kotlinx.android.synthetic.main.activity_main.*
import java.io.File
import java.io.RandomAccessFile

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val buffer = File("/sdcard/test.jp2").readBytes()
        val bmp = JJ2000Frontend.decode(buffer)
        imageView.setImageBitmap(bmp)
    }
}

以上です。

Android:Handlerで正確に周期的な実行

Handlerを使って 500ms間隔で 処理を実行する場合、以下のようにすると思います。ちなみにkotlinです。

    val handler = Handler()
    val runnable = object : Runnable {
        override fun run() {
            doHeavyWork() //重い処理
            handler.postDelayed(this, 500)
        }
    }

しかし、doHeavyWork()に処理時間が多くかかるばあい、その分の時間を差し引かなくてはいけません。例えば、 doHeavyWork() に300msかかるならpostDelayed(this, 200)にします。いや、そもそも doHeavyWork() にかかる時間は機種ごとに違うし、始めからわかってる事なんてほとんどないでしょう。

そこで、以下のようにします。

    val handler = Handler()
    val runnable = object : Runnable {
        override fun run() {
            val t = SystemClock.uptimeMillis()
            doHeavyWork() //重い処理
            handler.postAtTime(this, t + 500)
        }
    }

こんな感じにすると、いい感じになります。しかし、doHeavyWork()が500ms以上かかる場合はそもそも無理。video再生的なものならフレームをスキップするとか工夫が必要です。