程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 基礎:位圖和像素位

基礎:位圖和像素位

編輯:關於.NET

Windows® Presentation Foundation (WPF) 的保留模式圖形系統給 Windows 圖形編程帶來了巨 大變化。程序不再需要在系統要求時重新在屏幕上創建自己的可視外觀。這個復合系統會保留所有圖形數 字,並將其安排到整個可視外觀中。

保留模式圖形確實能夠簡化工作流程,但是對於 Windows 編程人員來說,“簡便”本身並 非是首要考慮目標。實際上,正是保留模式圖形系統與通知機制(依賴關系屬性)的組合,使 WPF 的靈 活性和功能得以展現。圖形對象(如路徑和畫筆)看起來仍“存在”於復合系統中,並繼續響 應屬性更改和圖形轉換,因此允許對這些對象進行數據綁定和設置動畫效果。

最近,我發現 WPF 位圖具有類似的動態性質:呈現的位圖仍會響應更改 — 不僅能夠響應圖形轉換(眾所周知),還 能響應位圖中實際像素位的更改。

展現這種動態響應的兩個位圖類是 RenderTargetBitmap 和 WriteableBitmap,它們是由 BitmapSource 派生的 9 個類中的成員;BitmapSource 是一個抽象類,是 WPF 中的所有位圖支持的基礎。程序可以將其中某個位圖對象與圖像元素一起顯示、使用 ImageBrush 類 將其制成一支平鋪畫筆,或者使用 ImageDrawing 類將其用作大型繪圖(可能混有矢量圖形)的一部分, 但無論以哪種方式使用,都不會在呈現位圖後就將其忽略。相反,位圖仍然位於可視復合系統中,並繼續 響應應用程序更改。

使用 RenderTargetBitmap

RenderTargetBitmap 是一個位圖,通過將 Visual 類型的對象傳輸到其表面即可進行有效繪制。要創建 RenderTargetBitmap 類型的新對象,唯一 的方法就是使用構造函數,並需要向該構造函數提供位圖的像素尺寸、以每英寸點數表示的水平和垂直分 辨率,以及 PixelFormat 類型的對象。

稍後,我將詳細介紹 PixelFormat 結構和相關的靜態 PixelFormats 類。要創建 RenderTargetBitmap 類型的對象,您必須將 PixelFormats.Default 或 PixelFormats.Pbgra32 用作 RenderTargetBitmap 構造函數的最後一個參數。無論使用哪一個,都可以 創建一個 32 位/像素的透明位圖。

最初,RenderTargetBitmap 對象是完全透明的。然後,您可 以使用 Visual 類型的對象(包括從 Visual 派生的類,如 FrameworkElement 和 Control)來調用 Render 方法,從而在此位圖上進行繪制。通過調用 Clear,可還原完全透明的圖像。如果當前顯示的是 該位圖,則上述調用將立即反映在顯示的位圖中。

圖 1 顯示了一個可演示 RenderTargetBitmap 的完整的小程序。該程序將創建一個寬 1,200 像素、高 900 像素的位圖,它們對應位圖對象的 PixelWidth 和 PixelHeight 屬性。每像素的寬度都是 4 個字節,因此該位圖占用的內存將超過 4 兆字 節。

圖 1 RenderTargetBitmapDemo

class RenderTargetBitmapDemo : Window
{
  RenderTargetBitmap bitmap;
  [STAThread]
  public static void Main()
  {
    Application app = new Application();
    app.Run(new RenderTargetBitmapDemo());
  }
  public RenderTargetBitmapDemo()
  {
    Title = "RenderTargetBitmap Demo";
    SizeToContent = SizeToContent.WidthAndHeight;
    ResizeMode = ResizeMode.CanMinimize;
    // Create RenderTargetBitmap
    bitmap = new RenderTargetBitmap(1200, 900, 300, 300,
                    PixelFormats.Default);
    // Create Image for bitmap
    Image img = new Image();
    img.Stretch = Stretch.None;
    img.Source = bitmap;
    Content = img;
  }
  protected override void OnMouseDown(MouseButtonEventArgs args)
  {
    Point ptMouse = args.GetPosition(this);
    Random rand = new Random();
    Brush brush = new SolidColorBrush(
      Color.FromRgb((byte)rand.Next(256), (byte)rand.Next(256),
                        (byte)rand.Next(256)));
    DrawingVisual vis = new DrawingVisual();
    DrawingContext dc = vis.RenderOpen();
    dc.DrawEllipse(brush, null, ptMouse, 12, 12);
    dc.Close();
    bitmap.Render(vis);
  }
  protected override void OnClosed(EventArgs args)
  {
    PngBitmapEncoder enc = new PngBitmapEncoder();
    enc.Frames.Add(BitmapFrame.Create(bitmap));
    FileStream stream = new FileStream("RenderTargetBitmapDemo.png",
                   FileMode.Create, FileAccess.Write);
    enc.Save(stream);
    stream.Close();
  }
}

調用 RenderTargetBitmap 構造函數還會指定 300 點/英寸的分辨率。將像素尺寸和分辨率相 結合可以創建一個寬 4 英寸、高 3 英寸的位圖。在與設備無關的 WPF 坐標系中,一英寸為 96 個單位 ,因此該位圖的設備無關寬度是 384 個單位,設備無關高度是 288 個單位。如果查看由 BitmapSource 定義的 Width 和 Height 屬性,就會看到這些數字。

RenderTargetBitmapDemo 程序使用圖像元素顯示未進行拉伸的位圖。它還會捕獲 MouseDown 事件。 每次單擊鼠標,該程序都會創建一個 DrawingVisual 對象 — 一個直徑為 ¼ 英寸的實心小 圓,然後調用 Render 方法將此圓添加到位圖圖像中。然後此圓就會出現在顯示的位圖上。您可以將此程 序看作一個簡單的畫圖應用程序,此程序在單個位圖中融合了呈現和存儲功能。

您可能知道(或 者可能會猜到),圖像元素通過調用在執行其 OnRender 方法的過程中傳遞給它的 DrawingContext 對象 的 DrawImage 方法來顯示位圖。當 RenderTargetBitmapDemo 程序更改位圖時,Image 類不會重復調用 其 OnRender 方法。在可視復合系統中,對位圖所做的這些更改發生在更深層。

RenderTargetBitmapDemo 程序終止時,它會以 PNG 文件格式保存合成的位圖。如果查看由 RenderTargetBitmapDemo 保存的位圖,您會發現其大小是 1,200 × 900 像素,並且每個圓的直徑 都是 75 像素 — 如果以 300 點/英寸為單位,則為 ¼ 英寸。

使用 WriteableBitmap

WriteableBitmap 類包含兩個構造函數,其中一個與 RenderTargetBitmap 構造 函數非常相似。前四個參數是位圖的像素尺寸和分辨率(以每英寸點數為單位)。第五個參數是一個 PixelFormat 對象,但它比 RenderTargetBitmap 的靈活度要高。對於需要調色板的位圖格式, WriteableBitmap 構造函數還提供了另外一個用於調色板的參數。

WriteableBitmap 的像素都將 初始化為零,其具體意義取決於像素格式。在許多情況下,位圖是全黑的。如果位圖支持透明度,則位圖 就是透明的。如果位圖有調色板,則整個位圖將顯示為調色板中的第一種顏色。

更改 WriteableBitmap 與更改 RenderTargetBitmap 有著很大的區別。對於 WriteableBitmap,您必須調用名 為 WritePixels 的方法,該方法將本地數組中的實際像素位復制到位圖中。很顯然,數組中數據的格式 和大小與位圖的尺寸和像素格式相匹配至關重要。

我們先看一個相對簡單的示例。圖 2 中顯示的 AnimatedBitmapBrush.cs 程序將創建一個 WriteableBitmap,並將其用作平鋪 ImageBrush 的基礎圖塊 ,其中的平鋪 ImageBrush 已由該程序設為窗口的 Background 屬性。然後,此程序會將計時器設為 100 毫秒,重復調用 WritePixels 來更改位圖。

圖 2 AnimatedBitmapBrush

class 

AnimatedBitmapBrush : Window
{
  const int COLS = 48;
  const int ROWS = 48;
  WriteableBitmap bitmap;
  byte[] pixels = new byte[COLS * ROWS];
  byte pixelLevel = 0x00;
  [STAThread]
  public static void Main()
  {
    Application app = new Application();
    app.Run(new AnimatedBitmapBrush());
  }
  public AnimatedBitmapBrush()
  {
    Title = "Animated Bitmap Brush";
    Width = Height = 300;
    bitmap = new WriteableBitmap(COLS, ROWS, 96, 96,
                   PixelFormats.Gray8, null);
    ImageBrush brush = new ImageBrush(bitmap);
    brush.TileMode = TileMode.Tile;
    brush.Viewport = new Rect(0, 0, COLS, ROWS);
    brush.ViewportUnits = BrushMappingMode.Absolute;
    Background = brush;
    DispatcherTimer tmr = new DispatcherTimer();
    tmr.Interval = TimeSpan.FromMilliseconds(100);
    tmr.Tick += TimerOnTick;
    tmr.Start();
  }
  void TimerOnTick(object sender, EventArgs args)
  {
    for (int row = 0; row < ROWS; row++)
      for (int col = 0; col < COLS; col++)
      {
        int index = row * COLS + col;
        double distanceFromCenter =
          2 * Math.Max(Math.Abs(row - ROWS / 2.0) / ROWS,
                 Math.Abs(col - COLS / 2.0) / COLS);
        pixels[index] =
          (byte)(0x80 * (1 + distanceFromCenter * pixelLevel));
      }
    bitmap.WritePixels(new Int32Rect(0, 0, 

COLS, ROWS), pixels, COLS, 0);
    pixelLevel++;
  }
}

COLS 和 ROWS 值都是常量,用於定義此位圖的像素尺寸。這兩個值還適用於平鋪畫筆 Viewport 矩形,並且還用在計時器的 Tick 事件處理程序中。像素格式的設置為 PixelFormats.Gray8, 這意味著位圖中的每個像素都由指示灰影的 8 位值表示 — 像素值 0x00 代表黑色,0xFF 代表白 色。由於 Gray8 格式不需要調色板,因此 WriteableBitmap 構造函數的最後一個參數設為空。

由於每個像素占 1 個字節,所以我稱之為“像素”的字節數組尺寸按 COLS × ROWS 計算即可。該像素數組將被定義為字段,因此無需在每次調用 Tick 事件處理程序時都重新創建該數組。 該數組中的數據必須從最頂行開始自左向右排列,然後是第二行,依此類推。Tick 事件處理程序中包含 兩個循環,分別針對位圖的行和列,但是它將這兩個值組合到了一個一維數組索引中:

int 

index = row * COLS + col;

WritePixels 不會接受多維數組。WritePixels 的第一個參數是一 個以像素坐標為單位的 Int32Rect 結構,用於指示位圖中要更新的矩形子集。Int32Rect 對象的 X 和 Y 屬性指示矩形左上角相對於位圖左上角的坐標;Width 和 Height 屬性指示此矩形的像素尺寸。要更新整 個位圖,可將 X 和 Y 設置為 0,並將 Width 和 Height 設置為位圖的 PixelWidth 和 PixelHeight 屬 性。稍後,我將介紹一下 WritePixels 的最後兩個參數。

必須承認,我原計劃為此位圖編碼的動 畫模式與此模式有些然不同,但此處偶然發現的模式看起來相當有趣 — 至少有小部分如此。圖 3 中顯示了其中一個圖像。

圖 3 AnimatedBitmapBrush 顯示

像素數組

如果位圖不是 1 個字節/像素的格式,且只更新了位 圖的矩形子集,則調用 WritePixels 的過程可能會更加復雜。這是一個需要了解的重要技術,因為靜態 BitmapSource.Create 方法需要使用這種相同的數組格式創建新位圖,BitmapSource 的 CopyPixels 方 法也需要使用此數組格式將位圖中的像素位復制到數組中。在所有這三個方法中,您都可以選擇使用 IntPtr 指向本地緩沖區,但下面我將重點介紹數組方法。

位圖中的像素總數可以根據 BitmapSource 類中定義的 PixelWidth 和 PixelHeight 屬性得出。BitmapSource 還會定義 PixelFormat 類型的只讀 Format 屬性,該屬性自身還會定義一個名為 BitsPerPixel 的只讀屬性,屬性 值范圍是 1 到 128。從一種極端的角度來說,一個字節可以存儲連續 8 像素的數據;而從另一種極端的 角度來說,每個像素需要 16 個字節的數據。您可能僅對像素位使用 byte、ushort、uint 或 float 的 數組。

您提供給 WritePixels 方法的 Int32Rect 對象可在位圖中定義一個矩形子集。像素數組 中的字節數必須包含足夠的數據,以填充 Int32Rect 對象指示的行數和列數。這有些復雜,因為多個像 素格式將在一個字節中存儲多個像素。對於這些格式,每行數據都必須從字節邊界處開始。

例如 ,假設您正在使用的位圖采用 4 位/像素的格式。位圖中您正在訪問或更新的矩形區域寬 5 像素,高 12 像素。這兩個值是您提供的 Int32Rect 對象的 Width 和 Height 屬性值。數組中的第一個字節包含前兩 個像素的數據,第二個字節包含接下來兩個像素的數據,但是第三個字節只包含第一行中第五個像素的數 據。下一個字節對應於第二行的前兩個像素。

每行數據都需要 3 個字節,整個矩形區域需要 36 個字節。為了幫助完成此計算,WritePixels 方法需要使用一個名為 stride 的參數,這是每行像素數據 的字節數。Stride 的常規計算方法為:

int stride = (width * bitsPerPixel + 7) / 

8;

寬度等於 Int32Rect 結構的 Width 屬性值。即使使用 ushort、uint 或 float 值數組, stride 值也始終以字節為單位。然後,您就可以使用以下方法計算數組中的總字節數:


 int 

dimension = height * stride;

如果使用的是 ushort、uint 或 float 數組,請分別除以 2、 4 或 8。

您也許還記得 Windows API 要求每行位圖數據都要從 32 位內存邊界開始,因此 stride 值必須是四的倍數。但在 WPF 中則無需如此。不過,如果方便的話,您可以將 stride 值設置為 大於通過公式計算出的值。例如,您可能使用每像素 1 個字節的位圖,但是您的數組類型是 uint,而不 是 byte。在這種情況下,數組中的每個元素都將存儲四個像素。即使並未嚴格要求,您可能仍希望在單 位邊界處開始數組的每一行,因為在目前的許多硬件平台上,對齊的副本通常比未對齊的副本執行速度快 。

位圖像素格式

位圖中的每個像素都由定義該位圖顏色的一個或多個位表示。在 WPF 中 ,特定的像素格式由結構類型為 PixelFormat 的對象表示。靜態 PixelFormats 類可定義 PixelFormat 類型的 26 個靜態屬性,供您在創建位圖時使用。這些屬性在圖 4 中分兩組顯示:可寫格式和不可寫格 式。除了 Bgr555、Bgr565 和 Bgr101010 這三個例外,屬性名稱中的任何數字都與每像素的位數相同。

圖 4 PixelFormat 類的靜態屬性

可寫格式 Indexed1 Indexed2 Indexed4 Indexed8   BlackWhite Gray2 Gray4 Gray8   Bgr555   Bgr32 Bgra32 Pbgra32 不可寫格式 默認   Bgr24 Rgb24 Bgr101010< /td> Cmyk32   Gray16 Rgb48 Rgba64 Prgba64   Gray32Float Rgb128Float Rgba128Float Prgba128Float

使用 BitmapSource.Create 創建位圖時,您可以使用 PixelFormats 類的任一 靜態屬性,但 PixelFormats.Default 除外。創建 WriteableBitmap 類型的位圖時,只能使用可寫格式 。

以單詞 Indexed 開頭的格式要求在靜態 BitmapSource.Create 方法或 WriteableBitmap 構造 函數中使用 ColorPalette 對象。每個像素都是 ColorPalette 對象的一個索引,因此這四個格式分別最 多可與 2、4、16 和 256 種顏色相關聯。如果實際的像素位未達到上限,則 ColorPalette 無需與最大 顏色數相同。

對於低於 8 位/像素的格式,字節中最明顯的位對應於最左側的像素。例如,對於 Indexed2 格式,字節 0xC9 等效於二進制 11001001,並對應於四個 2 位值(11、00、10 和 01)。那 麼,這四個值就分別對應於 ColorPalette 集合中的第 4 個、第 1 個、第 3 個和第 2 個顏色。

BlackWhite、Gray2、Gray4 和 Gray8 格式是采用 1、2、4 或 8 位每像素的灰影位圖。像素全 部為 0 代表黑色,而像素全部為 1 代表白色。

另外的五個可寫格式是顏色格式。字母 B、G 和 R 分別代表藍色、綠色和紅色。字母 A 代表 Alpha 通道,並指示位圖支持透明度。字母 P 代表預乘 Alpha,稍後我會對此進行介紹。

Bgr555 和 Bgr565 格式都要求采用 16 位(或 2 個字節)/像 素。Bgr555 格式對每個原色使用 5 個位(因而存在 32 個層次),余下 1 位。如果藍原色的位使用 B0 (最不明顯的位)到 B4(最明顯的位)表示,綠色和紅色也采用類似的表示法,則兩個連續的數據字節 就可以存儲這三種原色,如圖 5 所示。

圖 5 雙字節像素格 式

請注意,綠色位將遍布這 2 個字節。如果您了解到像素實際上是一個 16 位的無符號整數,最 先存儲的字節最不明顯,那麼這種安排將更有意義。圖 6 中的關系圖顯示了如何使用一個短整數來對原 色編碼。

圖 6 短整數像素格式

如果一來您就可以確保:圖 7 中顯示的 Gradient555Demo 程序 將創建此格式的位圖,並在其中寫入像素,顯示從左到右、從藍到綠的漸變。請注意,ushort 數組的大 小是總行數和列數的乘積。

圖 7 Gradient555Demo

class Indexed2Demo : Window
{
  const int COLS = 50;
  const int ROWS = 20;
  [STAThread]
  public static void Main()
  {
    Application app = new Application();
    app.Run(new Indexed2Demo());
  }
  public Indexed2Demo()
  {
    Title = "Bgr555 Bitmap Demo";
    WriteableBitmap bitmap = new WriteableBitmap(COLS, ROWS, 96, 96,
                    PixelFormats.Bgr555, null);
    ushort[] pixels = new ushort[ROWS * COLS];
    for (int row = 0; row < ROWS; row++)
      for (int col = 0; col < COLS; col++)
      {
        int index = row * COLS + col;
        int blue = (COLS - col) * 0x1F / COLS;
        int green = col * 0x1F / COLS;
        ushort pixel = (ushort)(green << 5 | blue);
        pixels[index] = pixel;
      }
    int stride = (COLS * bitmap.Format.BitsPerPixel + 7) / 8;
    bitmap.WritePixels(new Int32Rect(0, 0, COLS, ROWS), pixels,
              stride, 0);
    Image img = new Image();
    img.Source = bitmap;
    Content = img;
  }
}

此代碼演示了使用不同於對應於每像素字節數的 byte 類型的其他數組類型的好處。對於任何 行和列的像素地址,數組索引只是使用行與每列像素數相乘,再使用乘積與列取和所得的結果。

Bgr565 格式與 Bgr555 非常相似,但在前者中綠色使用了 6 位,眼睛對綠色最敏感。另外三個可寫 格式使用起來要容易得多。從藍色開始,所有格式都采用每像素 4 個字節。在 Bgr32 格式中,最後 4 個字節為零;不透明。在另外兩個格式中,第四個字節是 Alpha 通道。Alpha 值的范圍是 0x00(透明) 到 0xFF(不透明)。將像素視為 32 位無符號整數時,最不明顯的 8 位用於對藍色編碼,最明顯的 8 位為 0 或 Alpha 通道。

通常,位圖中字節的順序對應於屬性名中字母 B、G、R 和 A 的順序。不可 寫格式的列表最先列出的是幾個僅使用 16 或 24 位/像素的顏色格式。Bgr101010 格式使用 32 位/像素 ,但每個原色使用 10 位/像素。使用 32 位無符號整數表示像素時,最不明顯的 10 位用於藍色。 Cmyk32 格式對打印時使用的藍綠色,紫紅色,黃色和黑色級別進行編碼。

Gray16、Rgb48、Rgba64 和 Prgba64 格式對 16 位灰影和 16 位原色進行編碼。如果您使用的硬件和應用程序要求更高的顏色精度( 如醫療圖像),那麼您可能很高興現在能夠存儲和顯示分辨率如此高的位圖數據。然而,其他情況下就不 必非得使用這些格式。在 8 位/原色顯示器上,或保存為 8 位/原色文件格式時,會忽略額外的顏色精度 。

像素格式列表提供了四種使用單精度浮點值表示顏色級別和透明度的格式。這些格式基於伽瑪值為 1 的 scRGB 顏色空間,而不是慣用的伽瑪值為 2.2 的 sRGB 顏色空間。(請參閱我編寫的 《Applications = Code + Markup》一書中的第 24-25 頁查看相關說明。)浮點顏色值 0.0 對應於字節 值 0x00(黑色),浮點顏色值 1.0 對應於字節值 0xFF,但對於顯示設備,浮點顏色值可能會大於 1, 因為其顏色域寬度大於視頻顯示器。

PixelFormats 勘誤表

PixelFormats 文檔中的部分格式似乎有 些混亂。Gray16、Rgb48、Rgba64 和 Prgba64 格式都是基於伽瑪值 1 記錄的,但是除 Gray16 外,其余 格式又以 sRGB 格式進行了記錄,這就產生了矛盾。事實上這並不會引起混亂,因為只有 Float 像素格 式使用 scRGB 顏色格式和伽瑪值 1。

您可能希望使用一種通用方法將像素拆分到其顏色組件中,或者 基於顏色組件構建像素。PixelFormat 結構提供了一個名為 Mask 的屬性,這是 PixelFormatChannelMask 類型對象的集合。按藍色、綠色、紅色和 alpha 順序,每個顏色通道都有一個 對應的 PixelFormatChannelMask 對象。

PixelFormatChannelMask 結構定義了一個 Mask 屬性,這是 一個字節集合,字節數等於每像素字節數,還對應於像素的字節順序。例如,對於 Bgr555 格式,有三個 PixelFormatChannelMask 對象,每個對象包含 2 個字節。對於藍色,這兩個字節是 0x1F 和 0x00;對 於綠色,是 0xE0 和 0x03;對於紅色,是 0x00 和 0x7C。若要使用此數據,您必須派生自己的移位因子 。

我說過,您可以對 BitmapSource.Create 方法使用除 PixelFormats.Default 以外的任何 PixelFormats 成員,但對 WriteableBitmap 構造函數只能使用圖 4 中的可寫格式。如果查看 WriteableBitmap 文檔,您將發現一個備用構造函數,可使用任何 BitmapSource 對象創建 WriteableBitmap 對象。

實際上,您可以先使用圖 4 中的不可寫格式創建一個 BitmapSource 對象, 然後再基於此 BitmapSource 創建 WriteableBitmap。但其中存在以下限制:任何采用不可寫格式的位圖 都將轉換為 Bgr32 或 Pbgra32 格式,具體取決於 alpha 通道的狀態。

您可以將創建的所有位圖保存 到任何支持格式的文件中,這些文件格式包括 BMP、GIF、PNG、JPEG、TIFF 和 Microsoft Windows Media Photo。不過,在此過程中可能會將位圖數據轉換為其他格式。例如,另存為 GIF 文件時,總是先 將位圖轉換為 Indexed8 格式;另存為 JPEG 文件時,總是將位圖轉換為 Gray8 或 Bgr32。到目前為止 ,PixelFormat 和 BitmapEncoder 的任何組合都不能為您生成包含超過 8 位/原色的數據的文件。

預 乘 Alpha

PixelFormats 類的三個靜態屬性均以字母 P 開頭,代表預乘 Alpha。此技術用於改進部分 透明的像素的位圖呈現效率,僅適用於擁有 Alpha 通道的位圖。

假設您已創建了一個使用以下方式計 算顏色的 SolidColorBrush:

Color.FromArgb(128, 0, 0, 255)

那是一支半透明的藍色畫筆。顯示該畫筆時,顏色必須與顯示器表面的現有顏色結合。如果在黑色背 景上進行繪制,則合成的 RGB 顏色為 (0,0,128)。如果在白色背景上進行繪制,合成的顏色將為 (127 ,127,255)。這是一種簡單的加權平均計算。

下列公式的下標指示在現有表面上呈現部分透明像素的結果:

Rresult = [(255 – Apixel) * Rsurface + Apixel * Rpixel] / 255;
Gresult = [(255 – Apixel) * Gsurface + Apixel * Gpixel] / 255;
Bresult = [(255 – Apixel) * Bsurface + Apixel * Bpixel] / 255;

如果像素的 R、G 和 B 值已經乘以 A 值並除以 255,則可以提高此計算的速度。這樣即可取消每個 公式中的第二個乘法運算。例如,假設 Bgra32 位圖中的像素是 ARGB 值 (192, 40, 60, 255)。在 Pbgra32 位圖中,同一像素則為 (192, 30, 45, 192)。RGB 值已經乘以 Alpha 值 192 並除以 255。
對於 Pbgra32 位圖中的任何像素,R、G 或 B 值都不應大於 A 值。這樣就不會出現任何錯誤。雖然這些 值最大可為 255,但是您無法得到所需的透明度級別。

WriteableBitmap 應用程序

您可能會根據需要在應用程序中利用 WriteableBitmap 來顯示一些簡單的動態圖形(比如條形圖), 您會發現與使用 WPF 繪制相應的矢量圖形相比,更新位圖的速度更快。

也許 WriteableBitmap 最常見的用途是執行實時圖像處理和非線性轉換。TwistedBitmap 項目允許您 加載任何 8 位/像素或 32 位/像素的位圖,並使用 Slider 控件使該圖像在其中心周圍發生扭曲,如圖 8 所示。圖像越小,效果越好。

圖 8 扭曲的位圖

該程序使用 BitmapFrame.Create 加載來自文件的位圖,然後調用 CopyPixels 復制 pixelsSrc 數組 中的所有像素位。圖 9 中顯示的 SliderOnValueChanged 事件處理程序負責將像素從 pixelsSrc 轉換到 用於調用 WritePixels 的 pixelsNew 數組中。

圖 9 在 TwistedBitmap 中轉換

void SliderOnValueChanged(object sender,
RoutedPropertyChangedEventArgs<double> args)
{
if (pixelsSrc == null)
return;
Slider slider = sender as Slider;
int width = bitmap.PixelWidth;
int height = bitmap.PixelHeight;
int xCenter = width / 2;
int yCenter = height / 2;
int bytesPerPixel = bitmap.Format.BitsPerPixel / 8;
for (int row = 0; row < bitmap.PixelHeight; row += 1)
{
for (int col = 0; col < bitmap.PixelWidth; col += 1)
{
// Calculate length of point to center and angle
int xDelta = col - xCenter;
int yDelta = row - yCenter;
double distanceToCenter = Math.Sqrt(xDelta * xDelta +
yDelta * yDelta);
double angleClockwise = Math.Atan2(yDelta, xDelta);
// Calculate angle of rotation for twisting effect
double xEllipse = xCenter * Math.Cos(angleClockwise);
double yEllipse = yCenter * Math.Sin(angleClockwise);
double radius = Math.Sqrt(xEllipse * xEllipse +
yEllipse * yEllipse);
double fraction = Math.Max(0, 1 - distanceToCenter / radius);
double twist = fraction * Math.PI * slider.Value / 180;
// Calculate the source pixel for each destination pixel
int colSrc = (int) (xCenter + (col - xCenter) *
Math.Cos(twist)
(row - yCenter) * Math.Sin (twist));
int rowSrc = (int) (yCenter + (col - xCenter) *
Math.Sin(twist)
+ (row - yCenter) * Math.Cos(twist));
colSrc = Math.Max(0, Math.Min(width - 1, colSrc));
rowSrc = Math.Max(0, Math.Min(height - 1, rowSrc));
// Calculate the indices
int index = stride * row + bytesPerPixel * col;
int indexSrc = stride * rowSrc + bytesPerPixel * colSrc;
// Transfer the pixels
for (int i = 0; i < bytesPerPixel; i++)
pixelsNew[index + i] = pixelsSrc [indexSrc + i];
}
}
// Write out the array
bitmap.WritePixels(rect, pixelsNew, stride, 0);
}

使用位圖轉換時,執行反向轉換至關重要。如果檢查原始位圖中的每個像素並確定它在新位圖的位置 ,很可能會出現原始位圖中的不同像素映射到了新位圖中的同一像素的情況。這就意味著,將不會為新位 圖中的某些像素設置任何值!該圖像將出現一些“孔”。

相反,對於新位圖中的每個像素,您必須了解原始位圖中的哪些像素映射到特定的行或列。此方法可 確保新位圖中的每個像素都具有經計算得出的值。

請將您的問題和意見發送至 [email protected]

本文配套源碼:http://www.bianceng.net/dotnet/201212/751.htm

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved