1. 程式人生 > 實用技巧 >WPF顏色選擇器

WPF顏色選擇器

介紹 本文探討了幾個different實現一個顏色選擇器的方法。 表的內容 空間背景下載預覽版是什麼顏色的?我們的模型顏色怎麼樣?XYZ,它的許多形式重構檢視最後講話的興趣點對未來讀者歷史問題 背景 專案一開始修改一個由Ken Johnson和設計所以我想給他一個shoutout奠定了基礎。不幸的是,他的專案的問題可重用性差,有許多事情可以改善。 目標是減少多餘的程式碼,建立一致性,並定義基礎logic這樣額外的顏色空間可以輕鬆地新增和刪除。約翰遜定義a UserControl several元件:一探究竟,你將notice重複,根本沒有必要和使它difficult支援額外的模型XYZ和華爾街日報沒有乏味的複製和貼上。 現在,新增支援額外的顏色模型,您需要兩個essential元件: 一個檢視模型封裝了一個給定顏色空間和定義的邏輯相關的元件。結構表示一個顏色在一個給定的顏色空間和與其他顏色空間轉換的能力。 這讓我們有幾個優點: 從邏輯的觀點進一步分離。檢視may更容易安排和其他實現引入了一致性缺席。輸入給定的顏色空間的方向可能會改變。例如,在Photoshop中,輸入為CMYK安排水平而另一些垂直排列。你也可以指定一個顏色是否應該代表的視覺空間。c my RGB的倒數;因此,沒有必要來表示出來。你可以新增和刪除給定顏色空間在執行時支援。 還有一些缺點: 最初學習的邏輯有點複雜,不可能代表一個元件的價值不止一個方法(例如,Rgb值表示在一個範圍從0到255,但不能代表在其他範圍,比如從0到1)。 下載 你可能會發現最新版本並在GitHub演示。 預覽 空間是什麼顏色的? 顏色空間,也稱為a color模型(一、color系統),是一個抽象的數學模型,簡單地描述了射程of colors元組的數字,通常為3或4值或是color元件[1]只顏色空間你可能最熟悉的RGB, HSB,實驗室(由Adobe)。 顏色空間中實現這個解決方案是: CMYK * hsb奧軟實驗室祿luv rgb xyz yuv(或YXY) *邏輯表示只 也許最mysterious是CIE系列,由of XYZ,,實驗室,祿等等。非傳統的他們在現實世界中很少使用,容易被誤解又是;我從未見過的實際表示XYZ包括它在我的解決方案是必須的。 [1], http://www.arcsoft.com/topics/photostudio-darkroom/what-is-color-space.html 我們的模型顏色怎麼樣? 肯似乎正確的想法,乍看之下,但這個專案顏色空間的數量增加,所以我挫折和耐心。顏色對我來說最好的方法模型定義一個封裝的數學和邏輯結構和相應的檢視模型溝通這個邏輯檢視。 結構 請注意,為了簡便起見,我們省略了一些轉換邏輯…… 隱藏,收縮,複製Code

/// <summary>
/// Structure to define a color in <see cref="ColorSpace.Rgb"/>.
/// </summary>
[Serializable]
public struct Rgb : IColor, IEquatable<Rgb>
{
  #region Properties

  /// <summary>
  /// Specifies a <see cref="Rgb"/> component.
  /// </summary>
  public enum Component
  {
    /// <summary>
    /// Specifies the <see cref="Rgb.R"/> component.
    /// </summary>
    R,
    /// <summary>
    /// Specifies the <see cref="Rgb.G"/> component.
    /// </summary>
    G,
    /// <summary>
    /// Specifies the <see cref="Rgb.B"/> component.
    /// </summary>
    B
  }

  public const byte Maximum = byte.MaxValue;

  public const byte Minimum = byte.MinValue;

  public Color Color
  {
    get => Color.FromArgb(255, r, g, b);
  }

  readonly byte r;
  /// <summary>
  /// Gets the <see cref="Component.R"/> component (0 to 255).
  /// </summary>
  public byte R
  {
    get => r;
  }

  readonly byte g;
  /// <summary>
  /// Gets the <see cref="Component.G"/> component (0 to 255).
  /// </summary>
  public byte G
  {
    get => g;
  }

  readonly byte b;
  /// <summary>
  /// Gets the <see cref="Component.B"/> component (0 to 255).
  /// </summary>
  public byte B
  {
    get => b;
  }

  #endregion

  #region Rgb

  /// <summary>
  /// Initializes an instance of the <see cref="Rgb"/> structure.
  /// </summary>
  /// <param name="source"></param>
  public Rgb(Color source) : this(source.R, source.G, source.B) {}

  /// <summary>
  /// Initializes an instance of the <see cref="Rgb"/> structure.
  /// </summary>
  /// <param name="r"></param>
  /// <param name="g"></param>
  /// <param name="b"></param>
  public Rgb(int r, int g, int b) : this(r.Coerce(Maximum).ToByte(), g.Coerce(Maximum).ToByte(), b.Coerce(Maximum).ToByte()) {}

  /// <summary>
  /// Initializes an instance of the <see cref="Rgb"/> structure.
  /// </summary>
  /// <param name="_r"></param>
  /// <param name="_g"></param>
  /// <param name="_b"></param>
  public Rgb(byte _r, byte _g, byte _b)
  {
    r = _r;
    g = _g;
    b = _b;
  }

  /// <summary>
  /// Initializes an instance of the <see cref="Rgb"/> structure.
  /// </summary>
  /// <param name="source"></param>
  public Rgb(Cmyk source)
  {
    //Conversion logic...
  }

  /// <summary>
  /// Initializes an instance of the <see cref="Rgb"/> structure.
  /// </summary>
  /// <param name="source"></param>
  public Rgb(Hsb source)
  {
    //Conversion logic...
  }

  /// <summary>
  /// Initializes an instance of the <see cref="Rgb"/> structure.
  /// </summary>
  /// <param name="source"></param>
  public Rgb(Hsl source)
  {
    //Conversion logic...
  }

  /// <summary>
  /// Initializes an instance of the <see cref="Rgb"/> structure.
  /// </summary>
  /// <param name="source"></param>
  public Rgb(Lab source) : this(new Xyz(source)) {}

  /// <summary>
  /// Initializes an instance of the <see cref="Rgb"/> structure.
  /// </summary>
  /// <param name="source"></param>
  public Rgb(Lch source) : this(new Lab(source)) {}

  /// <summary>
  /// Initializes an instance of the <see cref="Rgb"/> structure.
  /// </summary>
  /// <param name="source"></param>
  public Rgb(Luv source) : this(new Xyz(source)) {}

  /// <summary>
  /// Initializes an instance of the <see cref="Rgb"/> structure.
  /// </summary>
  /// <param name="source"></param>
  public Rgb(Xyz source)
  {
    //Conversion logic...
  }

  /// <summary>
  /// Initializes an instance of the <see cref="Rgb"/> structure.
  /// </summary>
  /// <param name="source"></param>
  public Rgb(Yuv source) : this(new Xyz(source)) {}

  public static bool operator ==(Rgb left, Rgb right)
  {
    if (ReferenceEquals(left, null))
    {
      if (ReferenceEquals(right, null))
        return true;

      return false;
    }
    return left.Equals(right);
  }

  public static bool operator !=(Rgb left, Rgb right) => !(left == right);

  #endregion

  #region Methods

  public bool Equals(Rgb o)
  {
    if (ReferenceEquals(o, null))
      return false;

    if (ReferenceEquals(this, o))
      return true;

    if (GetType() != o.GetType())
      return false;

    return (R == o.R) && (G == o.G) && (B == o.B);
  }

  public override bool Equals(object o) => Equals((Rgb)o);

  public override int GetHashCode() => new { R, G, B }.GetHashCode();

  public override string ToString() => "R => {0}, G => {1}, B => {2}".F(r, g, b);

  public static double Linear(byte value) => Linear(value.ToInt32());

  public static double Linear(int value) => value.ToDouble() / Maximum.ToDouble();

  #endregion
}

每個結構都必須做到以下幾點: 為每個元件定義一個最大和最小值。強迫所有傳入的值到適當的範圍(例如,Rgb.R應該強迫一系列[0,255])。轉換為一個顏色of任何給定的表示,然後回來。為平等提供了檢查的能力。實現介面,IColor。 隱藏,複製Code

/// <summary>
/// Specifies a color.
/// </summary>
public interface IColor
{
  /// <summary>
  /// The color.
  /// </summary>
  Color Color
  {
    get;
  }
}

這都是表示 這就是事情可能很快就會變得令人困惑。RGB值most通常表示為一個整數範圍[0,255]。位元組型別商店RGB值完美;然而,RGB值也可以表示為一個無理數區間[0,1](255),除以值或範圍(0,2.55),(價值除以100);後者也可能最稀有、最實用。 儘管有這些許多表示,應該認識到只有一個結構。替代表示有時是必要的檢視模型時必須與結構,但應該沒有完全結構內部,以避免混亂。 有兩種主要的方式來表示給定元件的值: Logically 該值表示為一個整數範圍由of所有邏輯值由顏色空間定義。隱藏,Code影印件; Mathematically 該值表示為基於其邏輯對應項的百分比。 我們如何表示一個元件的值? 我們邏輯地表示這些值。有些值是使用byte、int或double來儲存的,這取決於精確性是否值得關注,甚至是否相關。只有在處理XYZ或XYZ派生的顏色空間(如LAB、LCH等)時,準確性才成為一個問題。 每個結構是否應該使用邏輯表示? 理想情況下,是的。這避免了在值從一個轉換方法傳遞到下一個轉換方法時,其表示形式出現混亂。 例如,在HSB中,色調分量有範圍[0,359],它對應於一個給定的度數或弧度。直接表示度[0,359]而不是比例[0,1]更直觀。 是否使用了數學表示? 以前的實現確實使用了這種表示,但後來被替換了。 , 隱藏,複製Code

H = 245 = 245 / 359
S = 60 = 60 / 100
B = 10 = 10 / 100

很容易意外地除以錯誤的值,所以在每一個與此有關的地方都要特別注意(注意,這應該只在模型中處理,而不是在原語中)。 關於原語需要注意的最後一點是,它們可以根據需要處理每個顏色空間之間的所有轉換。那麼其他的模型是什麼呢?例如,繫結到檢視,處理與轉換無關的數學邏輯,而是與檢視以及如何與檢視互動。與檢視的互動作用會隨著顏色空間的變化而發生數學上的變化。這是因為對於每個顏色空間的每個顏色元件,我們必須用數學方法在影象上繪製它的顏色表示,這是基於它的值、值的範圍,以及在某些情況下滑鼠相對於給定UI元素的x/y座標。 該模型指定了一些非常重要的事情: 它應該為色彩元件顯示什麼單位。每個元件的最小值和最大值以整數形式(不是原始形式!)例如,HSB中h分量的最小值為0.0,最大值為1.0;要找到整數形式的最大值,你必須知道h分量的可能最大值,並將其乘以h分量的值。色調的最大值是359,所以整型h分量的最大值是h分量的值* 359,這是有意義的,因為十進位制形式的最大值是1.0,當它乘以359,等於整型的最大值。如何畫出滑塊,只選擇元件的變化值如何繪製平面的顏色,這是由兩個對立元件向著相反的方向如何得到一個顏色從一個點(例如,當您點選顏色平面上的任何地方,我們得到這一點,基於所選擇的元件,轉換成一種顏色顯示所選的顏色。如何從一個顏色得到一個點 模型基類可能會讓事情有點混亂,但它最終幫助解決了我在開始時提到的複製/貼上困境。以前,每個表示顏色空間的使用者控制元件都包含在一堆方法中,這些方法對每個控制元件都是完全相同的(我知道這是一場噩夢)。但我發現,儘管每個顏色空間有許多不同,但都可以以超級統一的方式繪製:因此,您可以看到對每個元件模型中的基方法的呼叫。 隱藏,複製Code

base.UpdatePlane(Bitmap, ComponentValue, new Func<RowColumn, int, Rgba>((RowColumn, Value) =>
{
    return new Rgba(RowColumn.Column.ToByte(), RowColumn.Row.ToByte(), ComponentValue.ToByte());
}), new RowColumn(255, 255));

基本方法負責實際的繪製部分,它只是列舉我們想要繪製的影象的行/列,並根據模型中指定的轉換插入適當的顏色。點陣圖引數得到我們想要繪製的點陣圖:有兩個主要的點陣圖我們使用繪製,a)滑動點陣圖,b)彩色平面點陣圖。當其他兩個元件以相反的方向增加時,componentvalueparameter保持靜態。這種對立是通過為行和列指定最大值來實現的(依賴於它們對應的元件),而顏色是通過將這些值插入到一個基本型別中來建立的,該型別處理實際的顏色轉換。我們用Func返回獲得的顏色,它將公開當前行和列。一旦這個函式被返回,基方法處理分配這個顏色到它對應的畫素位置。對我來說已經夠好了! 現在讓我們看看基本方法: 隱藏,收縮,複製Code

unsafe
{
  Bitmap.Lock();

  int CurrentPixel = -1;
  byte* Start = (byte*)(void*)Bitmap.BackBuffer;

  var u = Unit.Value;
  u.Row = u.Row / 256.0;
  u.Column = u.Column / 256.0;

  double CurrentRow = u.Row * 256.0;

  for (int Row = 0; Row < Bitmap.PixelHeight; Row++)
  {
    double CurrentCol = 0;
    for (int Col = 0; Col < Bitmap.PixelWidth; Col++)
    {
      var c = Action.Invoke(new RowColumn(CurrentRow, CurrentCol), ComponentValue);

      CurrentPixel++;
      *(Start + CurrentPixel * 3 + 0) = c.B;
      *(Start + CurrentPixel * 3 + 1) = c.G;
      *(Start + CurrentPixel * 3 + 2) = c.R;
      CurrentCol += u.Column;
    }
    CurrentRow -= u.Row;
  }
  Bitmap.AddDirtyRect(new Int32Rect(0, 0, Bitmap.PixelWidth, Bitmap.PixelHeight));
  Bitmap.Unlock();
}

在處理指標時,我們被迫使用不安全的程式碼,這一點尤為重要。上面你會發現我發現的最快的方法之一,以繪製圖像,甚至與可疑的轉換演算法。如您所見,有一些值保持靜態,這是有原因的。 這個方法的最後一個引數是RowColumn型別的Unit,它指定要遞增/十二月的單位從行/列和是唯一的顏色空間的對立元件值。起始行總是最後一行,行單元從它遞減。這是因為影象是256個畫素×256個畫素的,相反分量的最大值是不一樣的。考慮一下:如果顏色空間是RGB,甚至不需要這個額外的數學運算,因為任何RGB值的範圍總共包含256個值!這可能是這個演算法中最令人困惑的方面之一,但希望它的組織方式對每個人都有意義。 滑塊的繪製非常相似,除了為一行找到的顏色對所有列重複,但對所有行不同。 XYZ,它的許多形式 如果有一件事是肯定的,那就是1931年讓數學家們感到困惑。這是因為與其只開發一種顏色空間,他們認為最好是開發多種顏色空間,以及上述多種顏色空間的多種變體。其中之一就是CIE XYZ,有時被稱為CIE 1931年或CIE 1964:認識到兩者之間的差異(老實說,我在理解自己方面仍有問題)以及究竟是什麼讓兩者如此不同是很重要的。 根據我的理解,XYZ是這樣工作的: XYZ充當其他CIE模型(如LAB、LCH、LUV等)的“門戶”;即。,為了從LAB計算RGB,您將首先將LAB轉換為XYZ,然後將XYZ轉換為RGB。XYZ本身有許多變體,這是由“光源”和“觀察者”決定的。看起來有很多光源可供選擇,但使用最廣泛的是D65,我的解決方案預設使用它;為了方便起見,我定義了其他6個函式,其餘的函式我找不到值。事實上,雖然F2和F7是最受歡迎的,但實際上有一個完整的系列從F1開始,到F11結束,而且可能會走得更遠。有兩個主要的觀測者:1931年或2度,1964年或10度;1931年是我選擇的觀察者。光源本質上決定了X, Y的最大值和最小值。與Y一致,幾乎總是有一個最大值1.0。所有光源的最小值為0。據我所知,有一種“理論”光源是“E”,不清楚是否應該用它來表示。不確定是否所有光源都能準確地表示出來。我的XYZ轉換似乎是正確的,但似乎是錯誤的,這在LCH實現.CIE實驗室和Hunter實驗室是完全不同的模型!在我的解決方案中使用了CIE LAB,需要與CIE XYZ進行轉換;據我所知,Adobe和繼續使用獵人lab.尚不清楚什麼是愛情的最大和最小值,不過,我懷疑它是0到1 L以及1 - 1 U和v .為此,愛是排除在預設情況下,但可以包含在任何時候對於那些喜歡實驗,如自己又是;大多數的XYZ變種看起來或多或少是一樣的,但如果有一種方法可以快速和容易地改變光源和觀察者,如果這樣想的話。幾乎沒有什麼數學研究來證明我的一些觀察結果,所以有很多推論和假設。 重構檢視 讓我對Ken的解決方案不滿意的一件事是,每個顏色空間都有一個單獨的使用者控制元件。我的意思是,為什麼?它們都有相同的特性,儘管從技術上講,有些與其他的有某種或那種不同,但我認為沒有理由這樣做。MVVM和繫結的存在是有原因的,畢竟。 說到這裡,讓我們來看看新的觀點: 隱藏,收縮,複製Code

<ItemsControl ItemsSource="{Binding Models, ElementName=PART_ColorPicker}" VerticalAlignment="Center">
  <ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
      <WrapPanel Orientation="Horizontal"/>
    </ItemsPanelTemplate>
  </ItemsControl.ItemsPanel>
  <ItemsControl.ItemTemplate>
    <DataTemplate DataType="{x:Type local:ColorSpaceModel}">
      <DataTemplate.Resources>
        <Common.Mvvm:BindingProxy x:Key="ComponentProxy" Data="{Binding Mode=OneWay, RelativeSource={RelativeSource Self}}"/>
      </DataTemplate.Resources>
      <local:ColorSpaceView x:Name="PART_Components" ItemsSource="{Binding Components}" Margin="0,0,0,15">
        <local:ColorSpaceView.ItemTemplate>
          <DataTemplate DataType="{x:Type local:ComponentModel}">
            <DataTemplate.Resources>
              <Common.Data.Converters:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
            </DataTemplate.Resources>
            <local:ComponentView
              ColorSpaceModel="{Binding DataContext, Mode=OneWay, RelativeSource={RelativeSource AncestorType={x:Type local:ColorSpaceView}}}"
              Color="{Binding SelectedColor, RelativeSource={RelativeSource AncestorType={x:Type local:ColorPicker}}}"
              ComponentModel="{Binding Mode=OneWay}"
              CurrentValue="{Binding CurrentValue}">
              <Grid>
                <Grid.ColumnDefinitions>
                  <ColumnDefinition Width="Auto" />
                  <ColumnDefinition Width="50" />
                  <ColumnDefinition Width="15" />
                </Grid.ColumnDefinitions>
                <RadioButton
                  Checked="OnComponentChecked"
                  Content="{Binding ComponentLabel}"
                  GroupName="ColorSpace"
                  IsChecked="{Binding IsEnabled}"
                  HorizontalAlignment="Center"
                  Margin="5,0"
                  VerticalAlignment="Center"
                  Tag="{Binding Mode=OneWay}"
                  Visibility="{Binding CanSelect, Converter={StaticResource BooleanToVisibilityConverter}}"/>
                <TextBlock
                  HorizontalAlignment="Center"
                  VerticalAlignment="Center"
                  Margin="5,0"
                  Text="{Binding ComponentLabel}"
                  Visibility="{Binding CanSelect, Converter={StaticResource BooleanToVisibilityConverter}, ConverterParameter=Inverted}"/>
                <Controls.Common:AdvancedTextBox
                  Grid.Column="1"
                  HorizontalAlignment="Center" 
                  HorizontalContentAlignment="Center"
                  VerticalAlignment="Center"
                  Text="{Binding CurrentValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                  Width="40"/>
                <TextBlock
                  Grid.Column="2"
                  Text="{Binding UnitLabel}"
                  VerticalAlignment="Center"/>
              </Grid>
            </local:ComponentView>
          </DataTemplate>
        </local:ColorSpaceView.ItemTemplate>
      </local:ColorSpaceView>
      <DataTemplate.Triggers>
        <DataTrigger Binding="{Binding Orientation}" Value="Horizontal">
          <Setter TargetName="PART_Components" Property="ItemsPanel">
            <Setter.Value>
              <ItemsPanelTemplate>
                <Controls.Common:Spacer Spacing="0,0,5,0" Orientation="Horizontal"/>
              </ItemsPanelTemplate>
            </Setter.Value>
          </Setter>
        </DataTrigger>
        <DataTrigger Binding="{Binding Orientation}" Value="Vertical">
          <Setter TargetName="PART_Components" Property="ItemsPanel">
            <Setter.Value>
              <ItemsPanelTemplate>
                <Controls.Common:Spacer Spacing="0,0,0,10"/>
              </ItemsPanelTemplate>
            </Setter.Value>
          </Setter>
        </DataTrigger>
      </DataTemplate.Triggers>
    </DataTemplate>
  </ItemsControl.ItemTemplate>
</ItemsControl>

該檢視主要包括以下內容: 存放所有可用顏色模型的ItemsControl。為了達到和之前一樣的效果,所有的顏色空間都放在一個包裹板中,也許會給人一種它們仍然在網格中的錯覺。然後顏色空間由一個叫做ColorSpaceView的控制元件表示,它除了促進顏色選擇器控制元件和顏色空間之間的交流之外什麼也不做。每個顏色空間模型都有一個元件陣列,這些元件是在初始化模型時新增的。這些繫結到ColorSpaceView。每個顏色元件都由控制元件元件視圖表示,它既方便了顏色空間和它的每個單獨元件之間的通訊,也處理了大多數與檢視相關的問題。其中一個問題是能夠根據文字輸入和文字選擇的顏色改變顏色分量值,而不會導致任何一方陷入無限迴圈;例如,如果一個屬性更改,我們必須更改另一個屬性,但如果其他屬性更改,將導致另一個屬性再次更改。關注這些變化可以解決這個問題。如果一個顏色空間具有水平方向(到目前為止,只有CmykModel),那麼它將顯示水平方向,而不是預設的垂直方向。 最後的評論 除了原語、模型和重構之外看,其他的都是一樣的你會發現在肯的專案。不同的是,在每個地方肯選擇不使用繫結,我選擇使用繫結。這需要使用few轉換器(如將十六進位制轉換成一個顏色,一個顏色一個SolidColorBrush等等),並帶走了太多多餘的程式碼我以前抱怨。 另一個重大變化是我選擇去一個顏色選擇器設計與選擇隱藏和顯示元素,而肯有能力顯示不同型別的農戶與α(例如,顏色選擇器滑塊和一個沒有)。在我看來,我不希望看到任何原因所以many種類的拾荒者和即使這樣,似乎更多的邏輯只是使toggling元素的可見性,而不是定義全新的控制又是; 的興趣點 斷開連線演算法;基於這些發表提醒EasyRGB。 , 問題的讀者 你認為它是不必要的,有這麼多顏色空間模型在一個顏色選擇器或越多越好?對我來說,它總是開心! 歷史 2016年9月17日 最初的釋出 未來 本文中的程式碼是開源專案的一部分,現在,Imagin.NET又是; 本文轉載於:http://www.diyabc.com/frontweb/news8365.html