<<20152017>>

2016-2-28[日] libharu を使ってjpg画像を右綴じpdfに変換

本の自炊で右綴じ化のために Acrobat を立ち上げるのは面倒で、他のフリーのpdfツールもたいていGUIで面倒そうで手頃なのが見つからないのでコマンドラインツールを作ってみることにした。

pdf作成で手頃そうなライブラリとして libharu(libhpdf.lib)というのがあったのでそれを採用... なんだけど、pdf 読込がよくわからず(出来ないのか出来ても面倒なのか...)。

もっとも jpg画像からpdf化に imagemagick を使ってるのだからその工程も自作ツールでやればよい話、と気づけばわりと簡単だった。(imagemagick では空白や日本語のあるファイル名の扱いに難ありだったし)

まず複数のjpgから1つのpdf生成するのは jpeg_demo.c あたりをいじって以下

#include <stdio.h>
#include <setjmp.h>
#include <hpdf.h>

static jmp_buf s_jmp_buf_env;

static void errorHandlerForHaru(HPDF_STATUS err, HPDF_STATUS   detNo, void *udat) {
    printf ("ERROR: err=%04X, detNo=%u\n", (HPDF_UINT)err, (HPDF_UINT)detNo);
    longjmp(s_jmp_buf_env, 1);
}

int main (int argc, char *argv[]) {
    if (argc < 2) {
        printf("usage> jpg2pdf jpgfile(s).jpg ...\n");
        return 1;
    }
    HPDF_Doc    pdf = HPDF_New (&errorHandlerForHaru, NULL);
    if (!pdf) {
        printf ("error: cannot create PdfDoc object\n");
        return 1;
    }
    if (setjmp(s_jmp_buf_env)) {
        HPDF_Free (pdf);
        return 1;
    }
    HPDF_SetCompressionMode (pdf, HPDF_COMP_ALL);
    /*☆*/
    for (size_t i = 1; i < argc; ++i) {
        HPDF_Page   page = HPDF_AddPage (pdf);
        if (!page)
            return 1;
        HPDF_Image  image   = HPDF_LoadJpegImageFromFile(pdf, argv[i]);
        if (!image) {
            printf("load error %s\n", argv[i]);
            return 1;
        }
        HPDF_REAL img_w = HPDF_REAL(HPDF_Image_GetWidth(image));
        HPDF_REAL img_h = HPDF_REAL(HPDF_Image_GetHeight(image));
        HPDF_Page_SetWidth (page, img_w);
        HPDF_Page_SetHeight(page, img_h);
        HPDF_Page_DrawImage (page, image, 0, 0, img_w, img_h);
    }
    HPDF_SaveToFile (pdf, "a.pdf");
    HPDF_Free (pdf);
    return 0;
}

コマンドライン引数で渡された jpgファイル(名) を順に突っ込んでるだけ。(コマンドライン引数なんで1オリジンだったり、出力ファイル名無精して a.pdf だったりするけれど)。 これだけで画像複数頁のpdfができちゃうので... ライブラリ、ありがたいです。

次に頁の開き方を設定。/*☆*/あたりに

    HPDF_SetPageLayout(pdf, HPDF_PAGE_LAYOUT_TWO_PAGE_RIGHT);

を挿入。ここでは見開き奇数ページ始まりにしてみた。

で、右綴じ。これについてはここらあたりをみて "ViewerPreferences" の Direction"に"R2L"を設定すりゃいいとのこと、libharuの HPDF_Catalog_SetViewerPreference が用途的にはそれなのだけど残念ながら"Direction"には未対応だったので見よう見まねで改造。

HPDF_STATUS  addR2L(HPDF_Doc pdf) {
    HPDF_Catalog    catalog     = pdf->catalog;
    HPDF_Dict       preferences = HPDF_Dict_New(catalog->mmgr);
    if (!preferences)
        return catalog->error->error_no;
    HPDF_STATUS ret = HPDF_Dict_Add(catalog, "ViewerPreferences", preferences);
    if (ret != HPDF_OK)
        return ret;
    ret = HPDF_Dict_AddName(preferences, "Direction", "R2L");
    if (ret != HPDF_OK)
        return ret;
    return HPDF_OK;
}

というのを用意、/*☆*/ の位置に呼び出しの addR2L(pdf); を挿入でok。

タイトルと著者名は (これも /*☆*/付近)

    HPDF_SetInfoAttr(pdf, HPDF_INFO_TITLE , "本のタイトル");
    HPDF_SetInfoAttr(pdf, HPDF_INFO_AUTHOR, "著者名");

と用意されたもので楽ちん... なんだが日本語が化けてしまう。

日本語関係の設定が必要のようだ。jfont_demo.c 等いろいろみて

    HPDF_UseJPEncodings(pdf);
    HPDF_UseJPFonts(pdf);
    HPDF_SetCurrentEncoder(pdf, "90msp-RKSJ-H");

を タイトルや著者を設定する前に記述しとく。
(3行目の"90msp-RKSJ-H"指定は、HPDF_UseJPEncodings(pdf);内で設定されているモノからプロポーショナルなゴシック体を選んだ)
これでacrobatのプロパティでタイトル&著者がちゃんと日本語で見れたので、めでたしめでたし、としておく。

HPDF_SetCurrentEncoder が肝、というか気づけずにここが一番苦労した。他のツールで生成したpdfとバイナリ見比べて、TitleやAuthorは7bit範囲はasciiだけどそれ以上だとUTF16(big endian)ベースになってるとか、utf8で別のxmlな記述スタイルになってるものとか... 結局utf16扱ってる処理から遡ってSetCurrentEncoderに行き着いた、と.


ということで、上記をもとに作ってみたコマンドラインツールはこちら
(※2/29追記: libharuビルド・バッチの不具合修正. exeは前と同じ)




2016-3-27[日] 2TBオーバーのHDDへのWindows10インストール

SATAが2ポートしかないN3150Bのdisk容量アップのためSSD512GB(Win8.1)を6TB(弱)のHDDにクローン、ついでにWin10へアップグレード。まずまず良好、と思いきや Cドライブのパーティションの直後に450MBほどの回復パーティションが作られていた。HDDのクローンで時短のためCドライブサイズ減らしたままだったので非常に困ってしまう。

しかたないので更地インストールし直すことに。 (win10は良くも悪くもハード構成一度認証してしまえば再インストールでproductキーの入力必要ないとのことなので多少 気が楽)
 別マシンでWin10の MediaCreationTool.exe 取ってきて インストールメディアとしてUSBフラッシュメモリを選択。

usbメモリからboot、概ね今までのwindows同様の手順で、HDDの選択にて全部のパーティションを一旦全部削除したところ、あれれ、未使用領域が2TBと4TB弱に分かれてる……2TBのほうは新規でフォーマット可能だけれど4TB弱のほうはエラー。ヘンに思いつつとりあえず2TBのほうにインストール。難なくOSインストールは済んだけれど、「ディスクの管理」で4TB弱の未使用パーティションは見えるけれど使用可能にすることが出来ない。

ググるとMBRでフォーマットした時の挙動のようだ。ここ数年bootドライブはSSDにして2TBオーバーなディスクにインストールしたことなかったので この手の事情 すっかり失念していた。GPTでインストールするには64bitOSでUEFIなBIOS必須、といっても今時のMBなら満たしてる。N3150Bも満たしてる……何か設定忘れてるんだろうな。

結論としては、DVDなりUSBメモリなりのwindowsインストールメディアを装着した状態でPCのBIOS画面にて最初にbootするドライブとして"UEFI:"が付いたモノを設定すればよい模様。

うち(N3150B)の場合、BIOSの"起動"の頁で、 起動装置 #1 に "UEFI: (USBメモリ名)" を設定、念の為 "USB:(USBメモリ名)" は無効にした。この状態でusbメモリからbootしてインストール開始すると、ちゃんと 6TB HDDを好きなようにパーティション分けできて osインストールも無事終了。
めでたしめでたし。

(わかれば他愛ないことだけど、手間取ったし、数年後にまたハマるかもしれないので、メモ書き)




2016-8-3[水] windows10 でスクリーンキーボードが勝手に出て鬱陶しい

windows10 にアップグレードしたマシンによってスクリーンキーボードが勝手に出るものがあった。出ないものもある。「設定」の「キーボード」ではどれも「スクリーンキーボード」offになってるのに...

結論からいえば、コントロールパネル側の簡単設定でもスクリーンキーボードのon/offが別途あり有効になっていた模様。 どうも古い仕組みと新しい仕組みが統合されてなかったのね。 と、いうことで覚書。

  • 「スタートボタン」→「設定」→「簡単操作」→「キーボード」→「スクリーンキーボード」 を off
  • 「コントロールパネル」→「コンピューターの簡単操作センター」→「マウスやキーボードを使わずにコンピューターを使用します」→「スクリーンキーボードを使用します」 のチェックボックスをoff



2016-9-3[土] opencvのコンパイル

opencvを使ってみたいけれど、公式配布のwin用バイナリは vs12,vs14 用 dll 版だけのようで個人的に好みでないのでソースをビルドしてみることに。

CMake を使う模様。てことで、慣れてない CMake 使用のメモ書き。
※ CMake GUI 版でよければこちらの記事で解説されている通りにやれば出来るのだけど、最終的には再生成用バッチを組みたいのでコマンドラインで作業。


バイナリ配布にもソース入っているけれど、別途ここから Source code のみの opencv-3.1.0.zip をダウンロードして展開、\opencv-3.1.0フォルダとする。

CMakeでソース環境外でビルドするには、まずビルド用フォルダ用意して そこをカレントにする必要があるらしい。

cd \opencv-3.1.0
md build
cd build

そして

CMake -G "Visual Studio 12 2013 Win64" -DBUILD_SHARED_LIBS=0  ..\ 

とすれば vs12 x64用 staticライブラリ(/Mt)版の sln 環境が生成され、生成された OpenCV.sln でビルドすれば .lib 群の出来上がり、と。

引数の ..\ は、ソース環境の基準となるディレクトリ位置の指定。ソース外ビルドするときは必須。

オプション -G は生成するビルドシステム(コンパイラ) の指定。どういう指定があるかは CMake --helpで確認。vs9 x86用なら "Visual Studio 9 2008" な具合。(CMake の運用では x86用と x64用とで sln(vc(x)proj) を別々に生成するのが普通ってことのよう)

オプション -D でopencv固有の BUILD_SHARED_LIBS を off にすることで dll(shared)ライブラリを止める指定になる。(つまりデフォルトはshared)

opencvビルドオプションはイッパイあり、opencv-3.1.0/直下の CMakeLists.txt の中を覗けば 説明付きでずらっと並んでるのでそれらを確認。


前記では staticライブラリ(/Mt)な生成になったけれど、dllランタイム版(/Md)にする場合は BUILD_WITH_STATIC_CRT をoffにする

CMake -G "Visual Studio 12 2013 Win64" -DBUILD_SHARED_LIBS=0 -DBUILD_WITH_STATIC_CRT=0 ..\ 

※ .lib .exeごとに vcxproj があって数が多いので、とても手作業で コンパイルオプションを設定し直す気になれないで、CMakeで生成し直しとなる。


また、うちの環境には Cuda sdkが入っていて、自動でcuda対応になってしまうのだけど非常にコンパイル時間が長くなってしまうため(数時間コース)、とりあえずcuda非対応にするために

-DWITH_CUDA=0 -DWITH_CUFFT=0 -DBUILD_CUDA_STUBS=0

を加えたりもした。
OpenCL対応も不要ならば

-DWITH_OPENCL=0 -DWITH_OPENCL_SVM=0 -DWITH_OPENCLAMDFFT=0 -DWITH_OPENCLAMDBLAS=0

を付ければよい、のだけど、こちらについては付けなくても問題なさそうだった。

その他 sample\をビルドしたければ -DBUILD_EXAMPLES=1 付けたり、逆にテスト関係不要ならば -DBUILD_TESTS=0 -DBUILD_PERF_TESTS=0 を付けたり、このへんはお好みで。


あと生成された.sln(.vcxproj)の中で opencv_videoio….libのビルドではバッチ経由でCMakeを使う設定のようで、sln生成CMake 実行時と同じ場所に CMake.exe がないと不味いようだった。(作業途中で 32bit版 CMake から 64bit版にインストールし直したのがいけないのだけど^^;)


それと試した範囲では、vc10exp,vc12,vc14は問題なかったけれど vc11 は何故か CMake でエラー終了になってしまった。

vc9(vc8) については、コンパイルできる.libも多いけれど、ソースで stdint.h が使われているライブラリもあってそれらがアウトだったり、OpenCL関係やvideoio関係がアウトなので、公式には未対応コンパイラの模様。
代用のstdint.hを用意してvs側でincludeパスを通し、OpenCL関係もoffにすればかなりコンパイル通るようになるけれど、opencv_videoio 関係は失敗する状態。
また vc8,vc9 に関してはIDE上でのコンパイルは試せるけれど msbuild を使ってのコマンドライン・ビルドは、msbuild.exeが即ハングするため駄目だった。


と、例によってlib生成だけでお腹いっぱいになってしまったので使用についてはまたそのうち。




2016-11-19[土] C++TemplateのつもりでC#Generics使ってハマる(初歩)

人様のソース改修でロクに知らないC#をここ1,2ヶ月さわってた。
といってもそのソースもC#慣れしたわけでないC系ユーザーが書いたような感じで ある意味助かったのだけれど、コピペ膨れなソースだったので、処理をまとめようと C++Template 的に Generics を使おうとして...ちょっとハマった。

C++だと

#include <stdio.h>
class Foo {
public:
    void Run() { printf("Foo!"); }
};
template<class T>
class FooMgr {
public:
    void Run() { T().Run(); }
};
class Bar : public Foo {
public:
    void Run() { printf("Bar!"); }
};
class BarMgr : public FooMgr<Bar> {
};
int main() {
    BarMgr barMgr;
    barMgr.Run();
}

で "Bar!!" と出力されるようなつもりで

class Foo {
    public void Run() { System.Console.Write("Foo!"); }
}
class FooMgr<T>
    where T : Foo, new()
{
    public void Run() { (new T()).Run(); }
}
class Bar : Foo {
    public new void Run() { System.Console.Write("Bar!!"); }
}
class BarMgr : FooMgr<Bar> {
}
class Test {
    public static void Main() {
        BarMgr barMgr = new BarMgr();
        barMgr.Run();
    }
}

のように書いてみたところ、実行したら "Foo!" が出力される、と。

初歩的な勘違いバグの類で、C# Generics って C++ Template ほど Macro的な強力さはなくて、この場合 継承class のラッパー的なモノでしかないのね。
void FooMgr<Bar>.Run() { (new Bar()).Run(); } のような実体(関数)の自動生成を期待しちゃってたのだけれど、FooMgr<Foo>.Run() に毛の生えた程度の実体が使い回される感じらしい。

where T : Foo で継承的な記述からとっとと気づけよなんだけど、ググって見よう見まね、要求を書いてるだけのつもりになってた。
修正は素直に virtual / override を使えばよく。

class Foo {
    public virtual void Run() { System.Console.Write("Foo!"); }
}
class FooMgr<T>
    where T : Foo, new()
{
    public void Run() { (new T()).Run(); }
}
class Bar : Foo {
    public override void Run() { System.Console.Write("Bar!!"); }
}
class BarMgr : FooMgr<Bar> {
}
class Test {
    public static void Main() {
        BarMgr barMgr = new BarMgr();
        barMgr.Run();
    }
}

どうせbase側からアクセスしないのだからと無精してTemplate使いたかったわけで多少本末転倒な気分もあるのだけれど、実作業では結局virtualすべき多態な事案になってしまったのでコレはコレでよしとしとく。

※も1つアホだったのは、new で継承元メソッド上書きしてたものだから、後から根本を virtual に変えても orverrideすべきものがnewのままでコンパイル通っちゃってハマるワナ




<<20152017>>