トップPC地図システム > パターンによる領域塗りつぶし

パターンによる領域塗りつぶし

はじめに

標準OSM地図は領域は適宜、それを特徴づけるパターンで塗りつぶしている。マイOSM地図も概ねこれに合わせる。 例えば、森林の場合には、葉っぱのアイコン(小さな画像データ)を散りばめる(下図)。

単なるハッチングのケースもある。C# の場合、ハッチングとアイコン塗りつぶしは別の方法で実現できる。 恐らく、ハッチングの方が高速であろう。 Android Javaではハッチングのような単純なパターンによる塗りつぶしはサポートされていないようである。

アイコンパターンによる塗りつぶしを、以前は、自前で画素単位の処理で実現していたが、 その後、PorterDuff演算で実現できることが分かった。

C#だけを考えると、ハッチングなど簡単な模様による塗りつぶしと任意のアイコンによる塗りつぶしを 分けることも考えられるが、Android Javaによる実装を考慮して、任意のアイコンによる塗りつぶしに一本化する。

任意の小さな画像データによるポリゴン塗りつぶしプログラム

OSMは多くの人の手によるものであり、アイコンパターンも様々な人により作られている。 タイルサイズと同じ 256x256画素のものもあれば、アイコン一つだけの小さなものもある。 256x256画素を超えるものもある。

Android Javaの場合には PorterDuff演算を行う都合上、アプリ起動時に、 256x256画素サイズのパターンデータを作成している。

C# の場合、その必要はなく、パターン画像を使って Brush を作成するだけでよい。 後は、単色ブラシでの塗りつぶしと同じである。

パターン画像ファイル自体にアイコンだけのものと地の色を含んだものがあるため、 地の色と重ねる必要があったり、なかったりする。

後者は、アイコンは同じで地の色を変えられるように配慮したものである。

プログラム上は、アプリ起動時の初期化処理またはレンダラーのコンストラクタで、 地の色とアイコンパターンを重ね合わせたもので、 ブラシを作成しておく。上記のように重ね合わせがいらないものもある。

プログラム的には Android Java に比べると C# は極めて簡単である。

因みに、上図の森林の葉っぱアイコンは 256x256画素サイズで、背景色は透明である(下図)。 標準OSM地図では、森林公園でも葉っぱアイコンが描画される。森林と公園では地の色は異なるが、 葉っぱアイコンは同じものが使われる。

以前、チェックしたところでは、地の色による塗りつぶしと葉っぱアイコンの重ね書きは同時ではなく、 タイミング的にも分かれていた。

大抵の場合は地の色とアイコンの重ね書きは同時であるが、"例外もあり"ということである。

マイOSM地図では、プログラムを極力シンプルにするため、地の色とアイコンの描画は一体化している。

なお、このアイコンパターンファイルは近年は svgファイルが多いため、 色を変更するなどのカスタマイズもやりやすい。

プログラムは次のようにした。アプリ起動後初めてのレンダリングで Brush を作成する。 単色での塗りつぶしではファイル読み込みはないが、アイコンパターンで塗りつぶす場合は パターンファイルの読み込みを最初の1回だけ行う。 以降は Dictionary(Java の HashMapに相当する)に登録したものを使う。

アイコンパターンに背景色がない場合は、引数は背景色とパターンになる。 最初に背景色で塗りつぶし、その後パターンを描画して Brush インスタンスを生成する。

    static Dictionary<long,Brush> dicBrush = new Dictionary<long,Brush>();

    public void FillPolygon(Graphics g, uint color, TileToRender tile) {
        FillPolygon(g, color, (Val)0, tile);
    }

    public void FillPolygon(Graphics g, Val pattern, TileToRender tile) {
        FillPolygon(g, 0, pattern, tile);
    }

    public void FillPolygon(Graphics g, uint color, Val pattern, TileToRender tile) {
        long key = (((long)pattern)<<32) | color;
        int intColor = unchecked((int)color);
        Brush br = null;
        if (!dicBrush.ContainsKey(key)) {
            br = color > 0 ? new SolidBrush(Color.FromArgb(intColor)) : null;
            if ((int)pattern > 0) {
                string file = "c:/map/img/" + pattern.ToString() + ".png";
                Image img = Image.FromFile(file);
                if (color > 0) {
                    Image imgBack = new Bitmap(img.Width, img.Height);
                    Graphics grBack = Graphics.FromImage(imgBack); 
                    grBack.FillRectangle(br, 0, 0, img.Width, img.Height);
                    grBack.DrawImage(img,  0, 0, img.Width, img.Height);
                    img = imgBack;
                }
                TextureBrush tbr = new TextureBrush(img);
                br = (Brush)tbr;
            }
            dicBrush[key] = br;
        } else {
            br = dicBrush[key];
        }
        if (br != null) {
            FillPolygon(g, br, tile);
        }
    }

マルチスレッド処理への対応

上記のプログラムではマルチスレッド処理で下のエラーが起きた。dicBrush にアクセスする区間に lock を かけたが、同じエラーが出た。Brush を同時利用できないようだ。

とりあえず、dicBrush は TileToRender に置くことにした。排他制御(lock) は要らない。 もっと、効率の良い方法があるかも知れない。

ハンドルされていない例外: System.InvalidOperationException: オブジェクトは現在他の場所で使用されています。
   場所 System.Drawing.Graphics.CheckErrorStatus(Int32 status)
   場所 System.Drawing.Graphics.FillPath(Brush brush, GraphicsPath path)
   場所 OSM.FillPolygon(Graphics g, Brush br, TileToRender tile)
   場所 OSM.FillPolygon(Graphics g, UInt32 color, Val pattern, TileToRender tile)
   場所 RenderLandcover.render(Graphics g, TileToRender tile, OSM[] osms, Int32 fromIndex, Int32 toIndex)
   場所 Renderer.render(Tile tile)
   場所 RenderThread.ThreadProc(Object obj)