|
~ To be, or not to be, or to think about it tomorrow. ~ null-i.net |
| AndroidJava/Pathで描く、その2 | |
|
パスで絵を描く、その2(2019-08-14、追記:2023-07-09) Pathですこし複雑な絵を描いてみる話です。
動的な絵を描く例†Pathで何かを書く時は、全体的に「動的に」書くことができます。 private void drawMoveEffect(Canvas canvas, float pf){
Path path = new Path();
Paint paint = new Paint();
paint.setColor(Color.GRAY); // ページの影の色
paint.setStyle(Paint.Style.FILL);
float base_x = canvas.getWidth(), base_y = canvas.getHeight();
if(pf > 1f) return; // 描画の終了
if(pf < 0) pf=0;
float mf=1f-pf;
float aj_x0 = -base_x*0.1f; // 左へのX調整。ページ端を少し画面の左へはみ出す
float aj_y0 = -10; // 上へのY調整。ページ端を少し画面の上へはみ出す
float aj_y1 = 10; // 下へのY調整。ページ端を少し画面の下へはみ出す
float x0 = base_x*mf +aj_x0; // ページの左端
float y0 = aj_y0-1f; // ページの左上
float y1 = base_y*(0.5f+0.5f*pf)+aj_y1; // ページの左下。画面50%から、100%へと下げる
float x2 = x0 + base_x*0.3f*mf; // ページ先端。
float y2 = y1 - base_y*0.3f*mf; // ページ先端。
path.moveTo(x0, y0); // 左上
path.cubicTo(x0 - base_x*0.03f*mf, base_y*0.3f*mf, // 左上から下へのハンドル1、下と少し左からゼロへ
x0-base_x*0.02f*mf, y1-base_y*0.12f*mf, // ハンドル2。左下を斜め上へ引っ張る感じで
x0+base_x*0.03f*mf, y1-base_y*0.03f*mf // 左下。一瞬遅れて到着する感じで。
);
path.cubicTo(x0+base_x*0.025f, y1+0.15f*mf, // ハンドル1、左下を斜め下へ引っ張る
base_x*0.8f, base_y+aj_y1, // ハンドル2、固定、動く左下を終点へ誘導する
base_x, base_y+aj_y1 // 右下、固定
);
path.cubicTo(base_x*(1-0.2f*(float)sin010(pf,1f)), base_y*(0.95f+0.05f*pf), // ハンドル1、ページ先端に膨らみをつける
x2, y2, x2, y2 // ハンドル2とページ先端
);
path.cubicTo(
x2, y2, // ハンドル1はページ先端と同じで
x0+base_x*0.05f*mf, y0+base_y*0.2f*(float)sin010(pf,1f), // この前の、ハンドル1のXYを入れ替えたイメージ
x0, y0
);
paint.setAlpha((int)(70 * sin01(pf, 1f))); // 徐々に表示だけど、sinで変化を加える
canvas.drawPath(path, paint);
}
public static double sin010(double now, double total){
double d;
double total2 = total * 2;
if(total2 == 0){
d = Math.sin(now); // 0除算の回避
}else {
d = Math.sin(Math.PI * 2 * ((now % (total)) / total2)); // 0 -> 1.0 -> 0 を1サイクルとする
}
return (d >= 0 ? d : d * -1.0);
}
public static double sin01(double now, double total){
double d;
double total4 = total * 4;
if(total4 == 0){
d = Math.sin(now); // 0除算の回避
}else {
d = Math.sin(Math.PI * 2 * ((now % total) / total4)); // 0 -> 1.0 を1サイクルとする
}
return (d >= 0 ? d : d * -1.0);
}
// 前述の関数をこんな風に呼ぶ // View などの Canvas を渡す float percent = System.currentTimeMillis() % 2000 / 2000f; // 2秒間かけて動かす drawMoveEffect(canvas, percent); ...こんな複雑なコードは、作者自身でも読むの辛いです。
ただ、 フィルターをかける&切り抜く†PaintでStyle.FILLを選んで、画面や絵にフィルターをかける場合があると思います。 SVGファイルをjavaのコードに変換する例†そもそも、 #!/usr/bin/perl -w
# gimp2 から拾った svgファイルから
# Android java の path へ変換する例
#
# gimp2から以下のようなファイルを
# 第一引数の ARGV[0]から読み込んでいく ---------------------------------------
# <?xml version="1.0" encoding="UTF-8" standalone="no"?> # ここは無視
# <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" # ここも無視
# "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> #無視
#
# <svg xmlns="http://www.w3.org/2000/svg" # 無視
# width="1.38889in" height="1.38889in" # 無視
# viewBox="0 0 100 100"> # 横幅を基準値として使う
# <path id="PATH_NAME" # 名称として使う
# fill="none" stroke="black" stroke-width="1" # 無視する
# d="M 62.93,28.55 # Path関数へ変換
# C 62.51,23.24 60.75,18.73 57.37,14.00 # Path関数へ変換
# (中略)
# 79.99,65.61 84.64,65.45 84.64,65.45 Z" /> # 数字は拾う。Zは無視する
# </svg> # "</" を終端に使う
#------------------------------------------------------------------------------
#
#
my %option;
my $svg_file = "$ARGV[0]";
my $out_file = "";
if(defined $ARGV[1] and length($ARGV[1]) > 0){
$out_file = "$ARGV[1]";
}
my $size = 0;
my $path_id = "";
my $path_id_count = 0;
my %used_id;
my $line = "";
my $is_skip = 0;
my $last_x = 0;
my $last_y = 0;
my $state = "NONE";
# state は以下の状態遷移を繰り返す
# PATH_ID path id を拾った状態
# -> PATH_SET とりあえず MかCを待つ
# PATH_M moveTo する
# PATH_C lineTo または cubicTo する
# -> PATH_END おわり
open($IN, "< $svg_file") or die "$! [$svg_file]\n";
if(length($out_file) <= 0){
if($svg_file =~ /\/([^\.\/]+)\..+/){
$out_file = $1 . "_out.txt";
}
elsif($svg_file =~ /([^\.]+)\..+/){
$out_file = $1 . "_out.txt";
}
else{
$out_file ="./output.txt";
}
}
if($out_file =~ /STDOUT/ or $out_file =~ /stdout/ or $out_file =~ /\-/){
# ファイルではなく標準出力に書き込む
$OUT = STDOUT;
}else{
# ファイルに上書きする
printf("out:%s\n", $out_file);
open($OUT, "> $out_file" . "_out.txt") or die "$! [$out_file]\n";
}
#
# svgファイルの座標を、java ソースコードに変換する
# 例として関数ごとまるっと作成するが、
# 実際に使う時は switch 文の中身だけを作成して、適宜差し替える
#
my $head_java=<<"EOL";
static Path get(Path ready_path, float left, float top, float width, float h, int id, boolean is_reverse){
Path pt;
if(ready_path == null) pt = new Path();
else pt = ready_path;
float x = left, y = top, w = width;
if(w <= 0 || h <= 0) return pt; // 長さ0なら意味がないので、すぐに戻す
if(is_reverse){
// 左右反転を試みる。ただしPATHの内容次第。
x = left + width;
w *= -1f;
}
long cur = System.currentTimeMillis();
switch (id){
EOL
# 注: EOL はヒアドキュメントなので、先頭にスペースを入れないこと
print $head_java;
#
# 以下、switch 文の中身として Path.lineTo やら Path.cubicTo を書く
#
while(<$IN>){
if(/viewBox="([\d\.]+) +[\d\.]+ +([\d\.]+) +[\d\.]+"/){
# 横幅をサイズ基準値(100%)として保持する
$size = $2 - $1;
dbg_printf("size:%.1f[%.2f - %.2f]\n", $size, $2, $1);
}
if(/path id="([^\s"]+)/){
$state = "PATH_ID";
$path_id = $1;
dbg_printf("id:%s\n", $path_id);
if(defined $used_id{$path_id} ){
# 1つのsvgファイルに同じ path id が二回出てきてしまった場合
err("--- already defined:%s\n", $path_id);
$is_skip = 1;
}else{
$path_id = $1;
$used_id{$path_id} = $path_id_count;
$path_id_count++;
$is_skip = 0;
printf($OUT "case %s:\n", $path_id);
}
}
if(($state eq "PATH_ID") and ($_ =~ / +d="(.+)/)){
$state = "PATH_SET";
$line = $1;
dbg_printf("start path(%s) ---\n", $line);
} elsif(($state eq "PATH_SET")
or ($state eq "PATH_M")
or ($state eq "PATH_C")
){
$line = $_;
}
while(($state eq "PATH_SET")
or ($state eq "PATH_M")
or ($state eq "PATH_C")
){
dbg_printf("%s\n", $line);
if(length($line) < 1 || $line =~ /^ *$/){
last;
}
if($line =~ /^ *([MC])/){
my $head = $1;
if($head =~ /M/ and $line =~ /M +([\-\d\.]+),([\-\d\.]+)(.*)/){
$state = "PATH_M";
$line = $3;
dbg_printf(" -PATH_M\n");
if($is_skip == 0){
my $a1 = pp($1 / $size), $a2 = pp($2 / $size);
printf($OUT " pt.moveTo(x+w*%.2ff, y+h*%.2ff);\n",
$a1, $a2 );
$last_x = $a1;
$last_y = $a2;
}
}
elsif($head =~ /C/ and $line =~ /C +([\-\d\.]+),([\-\d\.]+) +([\-\d\.]+),([\-\d\.]+) +([\-\d\.]+),([\-\d\.]+)(.*)/){
$state = "PATH_C";
$line = $7;
dbg_printf(" -PATH_C\n");
if($is_skip == 0){
my $a1 = pp($1 / $size), $a2 = pp($2 / $size), $a3 = pp($3 / $size), $a4 = pp($4 / $size), $a5 = pp($5 / $size),$a6 = pp($6 / $size);
if($last_x == $a1 && $last_y == $a2 && $a3 == $a5 && $a4 == $a6){
printf($OUT " pt.lineTo(x+w*%.2ff, y+h*%.2ff);\n",
$a5, $a6 );
}else{
printf($OUT " pt.cubicTo(x+w*%.2ff, y+h*%.2ff, x+w*%.2ff, y+h*%.2ff,x+w*%.2ff, y+h*%.2ff);\n",
$a1, $a2, $a3, $a4, $a5, $a6 );
}
$last_x = $a5;
$last_y = $a6;
}
}
}
elsif(
($line =~ /([\-\d\.]+),([\-\d\.]+) +([\-\d\.]+),([\-\d\.]+) +([\-\d\.]+),([\-\d\.]+)(.*)/)
and $state eq "PATH_C"){
$state = "PATH_C";
$line = $7;
dbg_printf(" -PATH_C(continue)\n");
if($is_skip == 0){
my $a1 = pp($1 / $size), $a2 = pp($2 / $size), $a3 = pp($3 / $size), $a4 = pp($4 / $size), $a5 = pp($5 / $size),$a6 = pp($6 / $size);
if($last_x == $a1 && $last_y == $a2 && $a3 == $a5 && $a4 == $a6){
printf($OUT " pt.lineTo(x+w*%.2ff, y+h*%.2ff);\n",
$a5, $a6 );
}else{
printf($OUT " pt.cubicTo(x+w*%.2ff, y+h*%.2ff, x+w*%.2ff, y+h*%.2ff,x+w*%.2ff, y+h*%.2ff);\n",
$a1, $a2, $a3, $a4, $a5, $a6 );
}
$last_x = $a5;
$last_y = $a6;
}
}
elsif($line =~ / Z(.*)/){
#$state = "PATH_Z"; # 現状は特に何もしない
dbg_printf(" -PATH_Z\n");
$line = $1;
}
elsif($line =~ /\/\>/){
$state = "PATH_END";
$path_id = "";
dbg_printf(" -PATH_END\n");
if($is_skip == 0){
printf($OUT " break;\n");
}
}
else{
err("unexpexted format:%s\n", $line);
last;
}
}
}
# svgファイルを読み終えたので、javaソースコードのswitch 文を閉じる
my $tail_java=<<"EOL";
default:
return pt;
}
return pt;
}
}
EOL
# 注: EOL はヒアドキュメントなので、先頭にスペースを入れないこと
print $tail_java;
#
# 最後に、これまで書いたPATHを static 変数化する
#
printf($OUT "//-------------\n");
printf($OUT "static final int\n");
my $before_key = "";
foreach (sort { $used_id{$a} <=> $used_id{$b}}(keys(%used_id))){
my $key = $_;
if(length($before_key) <= 0){
printf($OUT "%s = 1", $key);
}else{
printf($OUT ",\n%s = %s + 1", $key, $before_key);
}
$before_key = $key;
}
printf($OUT ";\n");
close($OUT);
#--- 四捨五入して丸める関数 ---------------
sub pp {
# 例では小数点以下2桁なので、%.2f とあわせて必要な桁数に調整する
# -> 桁数を丸めることで、
# 0.001 ピクセル程度の誤差なら同じ座標とみなす
# これをやらないと lineToが消え、cubicToが量産されてしまう
# -> ただ、最初から cubicToで精緻な絵を描く予定なら
# 今度は小数点4桁くらいに増やしてください
# 100 -> 10000 と、 %.2f -> %.4f です。
my $val = shift;
my $a = ($val > 0) ? 0.5 : -0.5;
return int($val * 100 + $a) / 100;
}
#--- 以下はデバッグログ関数 ----------------
sub dbg_printf {
#printf STDOUT @_ ;
#$|=1;
}
sub err {
printf STDOUT @_ ;
$|=1;
}
私の場合は、そもそもパスという専門用語?を知らない状態で、 |
|