19979


C#でWPFのアプリケーションを作成しています。  
その際に、多角形のBitmapをあらかじめ用意しておき、実行時に動的に多角形内の色を変更しようとしています。  
調べたところ、ロジックで開始点から同じ色だったら塗りつぶす色に変更していく方法が見つかりましたが、もっと簡単かつ処理が高速な方法がありましたら、ご教授ください。  
よろしくお願い致します。


CC BY-SA 3.0
2015-12-14T10:05:15.523
19966
2015-12-14T15:38:36.427
7212
question
c#
C#で多角形のBitmapの中身を塗りつぶすには


白黒のbitmapを用意しておいて、黒の部分のみ塗りつぶす処理を3種類の方法で行ってみました。

  1. SetPixel : 44,459(milliseconds)  
<http://dobon.net/vb/dotnet/graphics/colorbalance.html>

  2. LockBits : 1,692  
同上

  3. ColorMap : 1,247  
<http://dobon.net/vb/dotnet/graphics/setremaptable.html>

手持ちのPCで上記の処理を100回ずつ繰り返したところ、単色ならばColorMapとImageAttributesを用いた方法が一番速いという結果になりました。  
ただしアルゴリズムは全くチューニングしていません。  
最適化することで、異なる結果となるかもしれません。

なお、枠線の内側のみを塗りつぶす場合はOpenCVなどのライブラリを利用するのが手っ取り早いと思われます。

MainWindow.xaml

[code]

    <Window x:Class="FillPolygon.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:FillPolygon"
            mc:Ignorable="d"
            Title="MainWindow" Height="350" Width="525">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <Image Grid.Row="0" Name="MyImage"/>
            <StackPanel Grid.Row="1" Orientation="Horizontal">
                <RadioButton Name="RdoSetPixel" Content="SetPixel" IsChecked="True"/>
                <RadioButton Name="RdoLockBits" Content="LockBits"/>
                <RadioButton Name="RdoColorMap" Content="ColorMap"/>
                <Button Name="BtnCalc" Content="calc"/>
                <Label Name="LblCalc"/>
            </StackPanel>
        </Grid>
    </Window>
    
[/code]

MainWindow.xaml.cs

[code]

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Drawing;
    using System.Drawing.Imaging;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    
    namespace FillPolygon
    {
        /// <summary>
        /// MainWindow.xaml の相互作用ロジック
        /// </summary>
        public partial class MainWindow : Window
        {
            private Bitmap Original;
            private ImageSourceConverter Conv;
    
            public MainWindow()
            {
                InitializeComponent();
    
                //Src = new Bitmap(Assembly.GetExecutingAssembly().GetManifestResourceStream("FillPolygon.src.png"));
                Conv = new ImageSourceConverter();
    
                Loaded += MainWindow_Loaded;
                BtnCalc.Click += BtnCalc_Click;
            }
    
            private void BtnCalc_Click(object sender, RoutedEventArgs e)
            {
                var copy = new Bitmap(Original);
                var stopwatch = new Stopwatch();
                stopwatch.Start();
                Enumerable.Range(0, 100).ToList().ForEach(i =>
                {
                    if (RdoLockBits.IsChecked ?? false)
                    {
                        AdjustColorImage(copy, 255, 0, 255);
                        MyImage.Source = GetSource(copy);
    
                    }
                    else if (RdoSetPixel.IsChecked ?? false)
                    {
                        SetPixel(copy);
                    }
                    else
                    {
                        DrawMap(copy);
                    }
                });
                stopwatch.Stop();
                LblCalc.Content = stopwatch.Elapsed.TotalMilliseconds;
            }
    
            /// <summary>
            /// 指定した画像の色を補正する
            /// </summary>
            /// <param name="img">補正する画像</param>
            /// <param name="rValue">赤の増加値（-255～255）</param>
            /// <param name="gValue">緑の増加値（-255～255）</param>
            /// <param name="bValue">青の増加値（-255～255）</param>
            public void AdjustColorImage(Bitmap img,
                int rValue, int gValue, int bValue)
            {
                //1ピクセルあたりのバイト数を取得する
                System.Drawing.Imaging.PixelFormat pixelFormat = img.PixelFormat;
                int pixelSize = System.Drawing.Image.GetPixelFormatSize(pixelFormat) / 8;
                if (pixelSize < 3 || 4 < pixelSize)
                {
                    throw new ArgumentException(
                        "1ピクセルあたり24または32ビットの形式のイメージのみ有効です。",
                        "img");
                }
    
                //または次のように元の画像とは異なるPixelFormatでLockBitsすることも可能
                //この場合、UnlockBitsで元のPixelFormatに戻る
                //ただし、元のPixelFormatとLockBits時のPixelFormatが異なる場合は、
                //変更した色とは異なる色になる可能性がある
                //pixelFormat = PixelFormat.Format32bppArgb;
                //pixelSize = 4;
    
                //Bitmapをロックする
                BitmapData bmpDate = img.LockBits(
                    new System.Drawing.Rectangle(0, 0, img.Width, img.Height),
                    ImageLockMode.ReadWrite, img.PixelFormat);
    
                if (bmpDate.Stride < 0)
                {
                    img.UnlockBits(bmpDate);
                    throw new ArgumentException(
                        "ボトムアップ形式のイメージには対応していません。",
                        "img");
                }
    
                //ピクセルデータをバイト型配列で取得する
                IntPtr ptr = bmpDate.Scan0;
                byte[] pixels = new byte[bmpDate.Stride * img.Height];
                System.Runtime.InteropServices.Marshal.Copy(ptr, pixels, 0, pixels.Length);
    
                //すべてのピクセルの色を補正する
                for (int y = 0; y < bmpDate.Height; y++)
                {
                    for (int x = 0; x < bmpDate.Width; x++)
                    {
                        //ピクセルデータでのピクセル(x,y)の開始位置を計算する
                        int pos = y * bmpDate.Stride + x * pixelSize;
                        //新しい色を計算する
                        int newR = Math.Max(0, Math.Min(255, pixels[pos + 2] + rValue));
                        int newG = Math.Max(0, Math.Min(255, pixels[pos + 1] + gValue));
                        int newB = Math.Max(0, Math.Min(255, pixels[pos] + bValue));
                        //色を変更する
                        pixels[pos + 2] = (byte)newR;
                        pixels[pos + 1] = (byte)newG;
                        pixels[pos] = (byte)newB;
                    }
                }
    
                //ピクセルデータを元に戻す
                System.Runtime.InteropServices.Marshal.Copy(pixels, 0, ptr, pixels.Length);
    
                //ロックを解除する
                img.UnlockBits(bmpDate);
            }
    
            private void SetPixel(Bitmap bitmap)
            {
                for (int x = 0; x < bitmap.Width; x++)
                {
                    for (int y = 0; y < bitmap.Height; y++)
                    {
                        var c = bitmap.GetPixel(x, y);
                        if (c.R == 0 && c.G == 0 && c.B == 0)
                        {
                            bitmap.SetPixel(x, y, System.Drawing.Color.Yellow);
                        }
                    }
                }
                MyImage.Source = GetSource(bitmap);
            }
    
            private void DrawMap(Bitmap copy)
            {
                var c = System.Drawing.Color.Magenta;
                var map = new ColorMap
                {
                    OldColor = System.Drawing.Color.Black,
                    NewColor = c
                };
                var attrs = new ImageAttributes();
                attrs.SetRemapTable(new ColorMap[] { map });
                using (var g = Graphics.FromImage(copy))
                {
                    g.DrawImage(copy, new System.Drawing.Rectangle(0, 0, copy.Width, copy.Height), 0, 0, copy.Width, copy.Height, GraphicsUnit.Pixel, attrs);
                    MyImage.Source = GetSource(copy);
                }
            }
    
            private void MainWindow_Loaded(object sender, RoutedEventArgs e)
            {
                Original = new Bitmap("src.png");
                MyImage.Source = GetSource(Original);
            }
    
            private ImageSource GetSource(Bitmap bitmap)
            {
                using (var stream = new MemoryStream())
                {
                    bitmap.Save(stream, ImageFormat.Bmp);
                    stream.Seek(0, SeekOrigin.Begin);
                    return BitmapFrame.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
                }
            }
        }
    }
    
[/code]

※src.pngをresourceとして配置しています。


CC BY-SA 3.0
2015-12-14T15:38:36.427
19979
2015-12-14T15:38:36.427
9820
19966
answer