~ 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; } 私の場合は、そもそもパスという専門用語?を知らない状態で、 |
|