ICUライブラリで文字を一文字ずつ切り出したい。しかし切り出した結果をコンソール出力するのはもろもろの事情で難しく、ファイル出力は確認がめんどくさいのでウィンドウに描画する。
#include <windows.h> #include <unicode/ucnv.h> #include <unicode/brkiter.h> // 要リンク #pragma comment(lib, "icuuc.lib") // 以下のdllを要求される // icudt69.dll // icuuc69.dll LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); WCHAR szClassName[] = L"MY_ICU_TEST"; //ウィンドウクラス LPCWSTR fontname = L"MS P ゴシック"; HFONT hFont1; void WinPaint(HWND hWnd) { // 元の文字列の定義 std::u16string u16s = u"aあغ山👨👧經经"; ///////////////////////////// // ICUの設定 icu::UnicodeString usr(u16s.c_str()); UErrorCode err; icu::BreakIterator* bi = icu::BreakIterator::createCharacterInstance( icu::Locale::getDefault(),err); bi->setText(usr); //////////////////////////// //フォトの設定 HDC hdc; PAINTSTRUCT ps; hdc = BeginPaint(hWnd, &ps); SetTextColor(hdc, RGB(0, 0, 0)); //文字の色を設定 SetBkColor(hdc, RGB(230, 230, 230)); //文字の背景色を設定 HFONT backup = (HFONT)SelectObject(hdc, hFont1); //フォントを適応 TEXTMETRIC tm; GetTextMetrics(hdc, &tm); int TH = 10;
//////////////////////////// //文字を一文字(書記素)ずつ表示 int32_t current = bi->first(); while (current != icu::BreakIterator::DONE) { // 文字の長さを知る必要があるので、 // 「現在の文字の位置」と「前の文字の位置」が必要 int32_t prev = current; current = bi->next(); if (current == UBRK_DONE) { break; } int32_t count = current - prev;//文字の長さ //win32api のWCHARはutf16なのでただキャストするだけで変換できる TextOutW(hdc, 10, TH, (WCHAR*)(&u16s.data()[prev]), count);//文字を書く // こっちでもいい substr(現在位置,切り取る長さ) // std::u16string sub = u16s.substr(prev, count); // TextOutW(hdc, 10, TH, (WCHAR*)sub.data(), count);//文字を書く //改行 TH += tm.tmHeight+5; } SelectObject(hdc, backup); EndPaint(hWnd, &ps); }
//////////////////////////////////// //////////////////////////////////// // 表示用 //////////////////////////////////// //////////////////////////////////// int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) { MSG msg; BOOL bRet; WNDCLASSEX wc; HWND hWnd; ATOM atom; wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hCurInst; wc.hIcon = (HICON)LoadImage(NULL, MAKEINTRESOURCE(IDI_APPLICATION), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED); wc.hCursor = (HCURSOR)LoadImage(NULL, MAKEINTRESOURCE(IDC_ARROW), IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszMenuName = NULL; wc.lpszClassName = (LPCWSTR)szClassName; wc.hIconSm = (HICON)LoadImage(NULL, MAKEINTRESOURCE(IDI_APPLICATION), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED); if ((atom = RegisterClassEx(&wc)) == 0)return FALSE; hWnd = CreateWindow( MAKEINTATOM(atom), L"icu test", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 200, 500, // ウィンドウのサイズ NULL, NULL, hCurInst, NULL); if (!hWnd)return FALSE; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) { if (bRet == -1) { break; } else { TranslateMessage(&msg); DispatchMessage(&msg); } } return (int)msg.wParam; } LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { switch (msg) { case WM_CREATE: //Step1 フォント作成 hFont1 = CreateFont( 50, 0, //高さ, 幅 0, 0, FW_BOLD, FALSE, FALSE, FALSE, SHIFTJIS_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, VARIABLE_PITCH | FF_ROMAN, fontname); break; case WM_PAINT: WinPaint(hWnd); break; case WM_DESTROY: DeleteObject(hFont1); //Step4. HFONTオブジェクトを破棄 PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0; }
ICUライブラリを使って文字コードの変換を行うとき、例えば日本固有の文字コードに変換したいなら「Shift_JIS」や「EUC-JP」、中国固有の文字コードなら「GBK」「EUC-CN」など、変換先の文字コードを指定しなければならない。この文字コード名を「コードページ」というらしいが、Shift_JISやEUC-JPの他にどんな物があるか。
void test() { icu::UnicodeString us(u"いろはにほへと"); const char* code_page = "Shift_JIS"; // コードページ。他にどんな物があるか。 char dst_data[100]; int32_t len = us.extract(0, us.length(), dst_data, code_page); printf("%s (%d) \n",dst_data,len); }
マニュアルを読むと、どうやらConverter Namesという呼称でコードページを与えるらしい。そして使える名前一覧を取得する関数がいくつか用意されているらしい。
https://unicode-org.github.io/icu/userguide/conversion/converters.html#converter-names
例えば、ucnv_openAllNames は
Get a list of all known converter names.
と、「全ての知られているコンバータ名を取得する」と書かれている。使い方はこんな感じ。
auto name_enum = ucnv_openAllNames(&err); int32_t len; const char* name; while (name = uenum_next(name_enum, &len, &err)) { printf("%s\n", name); } uenum_close(name_enum);
ucnv_openAllNamesでイテレータを取得して、uenum_nextでイテレータを前に進めるような使い方をする。
ただこれで得られる内容は膨大すぎてよくわからない。
次に、ucnv_countAvailable は
Get a list of available converter names that can be opened.
と、「使えるコンバータのリストを取得する」と書かれている。使い方はこんな感じ。
int32_t convcount = ucnv_countAvailable(); for (int32_t i = 0; i < convcount; i++) { printf("[%03d] %s\n",i, ucnv_getAvailableName(i)); }
これでも200個以上出てくる上に、Shift_JISなどわかりやすい物がない。
そもそも同じコードページにも複数の表記法があるらしい。例えばShift_JIS,CP932,Windows-31J,MS932は(ほぼ)同じ意味になる。 ucnv_getAliases はそんな、あるコードページの仲間のコードページを探す。
int main() { UErrorCode err; const char* ALIAS = "Shift_JIS"; // 日本語のコードページ // ALIAS で表されるコードページの別名一覧を表示 uint16_t acount = ucnv_countAliases(ALIAS, &err); for (auto i = 0; i < acount; i++) { const char* code_page_arias = ucnv_getAlias(ALIAS, i, &err); printf("-- %s\n", code_page_arias); } getchar(); }
出力:
Shift_JIS を ucnv_getAliases にかけるとエイリアスが大量に出てくるのだが違いがあるのかよくわからない。拡張文字の有無など微細な違いがあるらしいCP932とShift_JISにそれぞれ変換してみた。
#include <fstream> #include <iostream> #include <unicode/ucnv.h> #include <unicode/unistr.h> #pragma comment(lib, "icuuc.lib") // 以下のdllを要求される // icudt69.dll // icuuc69.dll
void Converter(const icu::UnicodeString& us,const char* dstCode) { //内部形式の文字コードを目的の文字コードへ変換 char dst_data[100]; int32_t len = us.extract(0, us.length(), dst_data, dstCode); printf("length: %d\n", len); std::string textname = "C:\\dev\\icu-test\\converted-"; textname += dstCode; textname += ".txt"; ///////////////////////////////
// 文字コード変換した内容をファイル出力 std::fstream w( textname, std::ios::out | std::ios::binary | std::ios::trunc); w << dst_data; }
int main() { UErrorCode err; const char* ALIAS = "Shift_JIS"; // 日本語のコードページ //std::u16string src_data = u"いろはにほへと"; // 日本語 // NEC拡張文字 // ①②㌍ // IBM拡張文字 // 髙ⅱⅲ㈱ std::u16string jps = u"①②㌍髙ⅱⅲ㈱"; // ICUの内部表現へ変更 icu::UnicodeString ustr(jps.c_str()); Converter(ustr, "Shift_JIS");// コードページを指定して変換 Converter(ustr, "CP932"); getchar(); }
実行結果。特に変換できない文字は出てこなかった。
ICUライブラリのページではないようだがIBMのページなので、ここの一覧から選ぶことができそうである。いくつか拾い上げて ucnv_getAliases に与えると表のとおりの結果が返ってきている。
中国語やギリシャ語への変換例。確認方法が面倒。Converterは前述のものを使用。
ギリシャ語はWordでフォントをTimes New Romanにすると確認できる。
int main() { UErrorCode err; const char* ALIAS = "EUC-CN"; // 中国語のコードページ std::u16string src_data = u"什么"; // 中国語 icu::UnicodeString ustr(src_data.c_str()); Converter(ustr, ALIAS); getchar(); }
int main() { UErrorCode err; const char* ALIAS = "ibm737"; // ギリシャ語のコードページ std::u16string src_data = u"πανσέληνος"; // ギリシャ語 icu::UnicodeString ustr(src_data.c_str()); Converter(ustr, ALIAS); getchar(); }
本当は怖くないCP932
https://qiita.com/kasei-san/items/cfb993786153231e5413
以前やったのが2014年とかだったので更新する。
古い記事:
https://icu.unicode.org/download
公式サイトからICU4C系をクリックする。
ICU4CはC++。
ICU4JはJava。
その先のページの一番下のDownloadにある、Source and binary downloads are available on the git/GitHub tag pageのリンクへ飛ぶ。
一覧の中から、対応開発環境のものを選ぶ
リンクにicuuc.libが必要。また実行時にicudt69.dll , icuuc69.dllが必要。
#include <fstream> #include <unicode/ucnv.h> // icu::UnicodeString に必要 #include <unicode/unistr.h> // 要リンク #pragma comment(lib, "icuuc.lib") // 以下のdllを要求される // icudt69.dll // icuuc69.dll void Converter() { std::fstream r("C:\\dev\\icu-test\\shiftjis.txt"); std::string src_data; r >> src_data; //エラーを受け取る変数 UErrorCode err = U_ZERO_ERROR; //入力・出力の各文字コード char srcCode[] = "shift-jis"; char dstCode[] = "euc-jp"; //UnicodeStringに渡す文字列の文字コードを指定する UConverter* srccnv = ucnv_open(srcCode, &err); //入力された文字の文字コードを内部形式に変換 icu::UnicodeString srcstr(src_data.c_str(), src_data.length(), srccnv, err); //内部形式の文字コードを目的の文字コードへ変換 char* dst_data = new char[100]; srcstr.extract(0, srcstr.length(), dst_data, dstCode); using namespace std; std::fstream w("C:\\dev\\icu-test\\euc-jp.txt", ios::out | ios::binary | ios::trunc); w << dst_data; } int main() { Converter(); }
file-png-save-defaults関数を使うのだが、画像番号と一緒にdrawableを指定しなければならず、したがってそのdrawableのレイヤーしか保存できない。
全てのレイヤーをマージするにはgimp-image-merge-visible-layers関数を使えばいいが、エクスポートしたいだけでマージするわけにいかないので、gimp-image-duplicateで現在の画像をコピーし、それに対してマージを行い、保存した後でgimp-image-deleteで削除する
; 画像をPNG保存する自作関数。 ; Image 画像番号 ; Drawable レイヤー番号 ; outpath 保存先(ファイル名を含まない) ; filename ファイル名(.pngを含まない) (define (my-save-to-png Image Drawable outpath filename) ; 保存処理 (file-png-save-defaults RUN-NONINTERACTIVE Image Drawable (string-append outpath filename ".png") (string-append filename ".png") ) )
; 全てのレイヤーを統合してpngにエクスポート ; Image 画像番号 (gimp-image-list) で取得可能 ; outpath 出力先のパス。C:\\test\\ (define (my-save-png-as-single-image Image outpath ) (begin ; 現在の画像をコピーする (define dummydup '()) (set! dummydup (car (gimp-image-duplicate Image ) ) ) ; コピーした画像の全レイヤーをマージして一つの画像にする (gimp-image-merge-visible-layers dummydup CLIP-TO-IMAGE ) ; 保存に必要な、dummydupのdrawableを取得する (define activedrawable '()) (set! activedrawable (car (gimp-image-get-active-drawable dummydup ) ) ) (my-save-to-png dummydup activedrawable outpath (car (gimp-image-get-name Image) ); 画像の名前を取得してファイル名に指定 ) (gimp-image-delete dummydup) ) )
;; usage : ;; (gimp-image-list) ;; > (1 #(5)) ;; (my-save-png-as-single-image 5 "C:\\test\\") ;; > (#t)
Import Images as Planeを使えばする必要すらないが、Pythonコードからplaneにテクスチャを設定する場合。
import bpy # @brief Planeオブジェクトのサイズをテクスチャ画像に合わせて調整 # @param [in,out] plane Planeオブジェクト # @param [in] Image Textureノード # @param [in] ratio 何倍のサイズにするか # @return なし def TextureFixSize(plane,imgnode,ratio): maxsize = max( imgnode.image.size[0] , imgnode.image.size[1] ) plane.scale[0] = imgnode.image.size[0] / maxsize * ratio plane.scale[1] = imgnode.image.size[1] / maxsize * ratio # @brief テクスチャをplaneに設定する # @param [in,out] plane Planeオブジェクト # @param [in] filepath 画像データへのパス # @note マテリアルが設定され、Image Textureノードが存在している必要がある # @return なし def TextureFromFile(plane,filepath): #################### ## 画像ファイルを読み込み pngdata = bpy.data.images.load(filepath = filepath) #################### ## planeオブジェクトのImage Textureノードを取得 nodetree = plane.material_slots["Material.001"].material.node_tree imgnode = nodetree.nodes["Image Texture"] #################### ## Image Textureノードへ画像を設定 imgnode.image = pngdata #################### ## 張り付けたPlaneオブジェクトのサイズを画像サイズと同じ縦横比に合わせる TextureFixSize(plane,imgnode,1) act = bpy.context.active_object TextureFromFile(act,'C:/test/lake-6627781_640.jpg')
まず画像ファイルを読み込む
次にマテリアルからImage Textureノードを取得する
ノードに画像を設定
Image TextureノードのImageオブジェクトにsizeがある。
https://docs.blender.org/api/current/bpy.types.ShaderNodeTexImage.html
例えばアニメーションの長さを 500フレーム とする。この時、
F == 500
H == 250 (...500/2)
と定義する。
Particleの発生時間を、以下のように設定
start -(H-1)
stop H
次に、そのParticleをコピーし、、発生時間を以下のように設定
start H+1
stop F+H
500 frameのアニメーションであれば以下のようになる
まずアニメーションを500フレームに設定
次にparticleの発生時間を設定し、それをコピーした二つ目のparticleの設定も行う
particle1 : -249 …. 250
particle2 : 251 …. 750
blender のメッシュはNgonが可能なので、三角形だけカウントすると四角形以降がカウントされなかったりする。
mesh.calc_loop_triangles()関数を使えば、全て三角形として頂点を取り出すことができる。
注意点としてこれを呼び出したからと言ってモデルが三角形メッシュになるわけではないらしい。また三角形メッシュに変換した結果とこの結果が異なったりする。
import bpy
# @brief メッシュを構成する三角形を三頂点の組み合わせのリストで取得 # @param [in] obj オブジェクト def get_triangles(obj): ret = [] mesh = obj.data mesh.calc_loop_triangles() for tri in mesh.loop_triangles: ret.append([tri.vertices[0],tri.vertices[1],tri.vertices[2] ]) # [index0,index1,index2]のリスト return ret
# @brief 与えられたオブジェクトの重心を求める # @param [in] obj オブジェクト # @param [in] vid0 obj.data.verticesのindex # @param [in] vid1 obj.data.verticesのindex # @param [in] vid2 obj.data.verticesのindex # @return 三次元座標 <Vector ( x , y , z )> def calc_triangle_center(obj,vid0,vid1,vid2): mesh = obj.data tri_center = (mesh.vertices[vid0].co) +\ (mesh.vertices[vid1].co) +\ (mesh.vertices[vid2].co) tri_center /= 3.0 return tri_center
# @brief オブジェクトの全ての三角形の重心にsphereを配置する # @param [in] オブジェクト def disp_triangle_centers(obj): # 三角形をindex listで取得 PIDs = get_triangles(obj) for pid in PIDs: # 三角形の重心を算出 c = calc_triangle_center(obj, pid[0],pid[1],pid[2]) # 座標 c にsphereを配置 bpy.ops.mesh.primitive_uv_sphere_add( radius=0.05, segments=8, ring_count=8, enter_editmode=False, location=c )
# 使用例 myobj = bpy.context.active_object disp_triangle_centers(myobj)
まずコードを用意して、上記フォルダへ適当な名前で保存する
例 script-test.scm
; 画像をPNG保存する自作関数。この関数を呼び出したい ; Image 画像番号 ; Drawable レイヤー番号 ; outpath 保存先(ファイル名を含まない) ; filename ファイル名(.pngを含まない) (define (my-save-to-png Image Drawable outpath filename) ; 保存処理 (file-png-save-defaults RUN-NONINTERACTIVE Image Drawable (string-append outpath filename ".png") (string-append filename ".png") ) )
; GUIダイアログの項目を設定 ;; 参考 http://sampodo.la.coocan.jp/graphics/gimp2/gimp2_1.html (script-fu-register ; ; 呼び出す関数 ; メニュー上の情報 ; ダイアログの項目設定 ; "my-save-to-png" ;呼び出す関数 "PNGで保存" ;メニューに表示する項目 "テスト用スクリプト\ の説明文" ;説明文章 "空鳥夜鳥" ;制作者 "copyright 2021, szl" ;著作編表示 "2021/11/08" ;制作日時 "*" ;スクリプト関数が扱う画像のタイプ ; 以降、引数を指定するGUIパーツの作成、個数は任意。 ; ; 呼び出す関数が my-save-to-png であるので、 ; my-save-to-png の引数の順番と同じでなければいけない ; ; 指定方法 : ; GUIアイテムの種類 "表示用ラベル" デフォルト値 SF-IMAGE "Image" 0
SF-DRAWABLE "Drawable" 0 SF-STRING "パス\\" "C:\\test\\" ; テキストボックスになる SF-STRING "ファイル名" "default-name" )
; メニューに登録する (script-fu-menu-register ; 呼び出す関数名 "my-save-to-png" ; メニューのどの位置に項目を挿入するか "<Image>/File/オリジナル項目" )
save-layers関数を作成。
;; レイヤー一覧取得 ;; image (car (gimp-image-list)) で取得 (define (get-layer-list image) (vector->list (car (cdr (gimp-image-get-layers image) ) ) ) )
;; outpath 出力先のディレクトリ ;; filename ファイル名 拡張子なし ;; image 画像番号 ;; drawablelayer レイヤー番号 (define (save-to-image outpath filename image drawablelayer) (begin ; 保存情報を表示 (display "saving:") (display (string-append outpath filename ".png") ) (newline) ; 保存処理 (file-png-save-defaults RUN-NONINTERACTIVE image drawablelayer (string-append outpath filename ".png") (string-append filename ".png") ) ) )
;; outpath 出力先のパス ;; image 画像番号 ;; layerlist 残りのレイヤー一覧 (define (save-more-layers outpath image layerlist) ; 画像一覧が空リストでないなら保存 (if (not (null? layerlist) ) (begin ; リストの先頭要素をPNGで保存 (save-to-image outpath ; 出力先パス (car (gimp-layer-get-name (car layerlist))) ; ファイル名としてレイヤー名を取得 image ; 画像番号 (car layerlist) ) ; レイヤー番号リストの一番左 ; 残りのリストを保存(再帰) (save-more-layers outpath ; 出力先パス image ; 画像番号 (cdr layerlist) ) ; 残りのリスト ; ) ) )
;; レイヤーをPNGファイルに保存する関数 ;; outpath ファイルパス 例 C:\\test\\ ;; image 画像番号 (car (gimp-image-list))で取得可 (define (save-laysers outpath image) (save-more-layers outpath image (get-layer-list image) ) )
パスの文字列を加工したりとかいろいろ複合的に使ってみたやつ。
;---------------------------------- ;---------------------------------- ; フォルダから画像を読み込む ; ファイルパスからファイル名を取得する (define (my-extract-file-name text) (car (reverse (strbreakup text "\\") ) ) ) ; 画像を読み込んだ後、タブを表示する (define (my-tab-update image) (begin (gimp-display-new image) (gimp-displays-flush) image ) ) ; リストで与えられた画像ファイルを全て読み込む (define (my-open-all-images flist) (if (null? flist) '() (cons (my-tab-update (car (gimp-file-load 0 (car flist) (my-extract-file-name (car flist) ) ) ) ) (my-open-all-images (cdr flist) ) ) ) ) ; パスで指定したディレクトリから画像を読み込む。 ; パスの最後は\\*で終わっている必要がある ; 例 (my-open-images "C:\\dev\\*") (define (my-open-images pathnamelist) ( my-open-all-images (car (cdr (file-glob pathnamelist 0))) ) ) ;---------------------------------- ;---------------------------------- ; 画像をxcf形式で保存する ; 画像を一枚xcf形式で保存する。 ; ファイル名は元の名前+xcf (define (my-save-as-xcf-single image outdir basename) (gimp-xcf-save 0 image (car (gimp-image-get-active-drawable image) ) (string-append outdir basename ".xcf") (string-append basename ".xcf") ) ) ; outdirへ画像群を保存。outdirは"\\"で終わっている必要がある (define (my-save-images outdir images) (if (null? images) '() (begin (my-save-as-xcf-single (car images) outdir (my-extract-file-name (car (gimp-image-get-filename (car images) ) ) ) ;画像番号からファイルパスを取得しファイル名を切り出す ) (my-save-images outdir (cdr images) ) ) ) )
;(my-save-as-xcf-images "H:\\input\\*" "H:\\output\\") (define (my-save-as-xcf-images indir outdir) (my-save-images outdir (my-open-images indir) ) )