天天看點

第十三章:位圖(七)

在運作時生成位圖

所有這三個平台都支援BMP檔案格式,該格式可以追溯到Microsoft Windows的最開始。盡管它具有古老的傳統,但BMP檔案格式現在已經相當标準化,具有更多的擴充标題資訊。

雖然有一些BMP選項允許一些基本壓縮,但大多數BMP檔案都是未壓縮的。這種缺乏壓縮通常被視為BMP檔案的缺點,但在某些情況下它根本不是缺點。例如,如果要在運作時以算法方式生成位圖,則生成未壓縮的位圖而不是其中一種壓縮檔案格式要容易得多。 (實際上,即使你有一個庫函數來建立JPEG或PNG檔案,你也可以

将該函數應用于未壓縮的像素資料。)

您可以通過使用BMP檔案頭和像素資料填充MemoryStream,然後将該MemoryStream傳遞給ImageSource.FromStream方法,在運作時以算法方式建立位圖。 Xamarin.FormsBook.Toolkit庫中的BmpMaker類示範了這一點。它使用32位像素格式在記憶體中建立BMP,每個格式為8位,用于紅色,綠色,藍色和alpha(不透明度)通道。 BmpMaker類在編寫時考慮了性能,希望它可以用于動畫。也許有一天它會成為,但在本章中,唯一的示範是一個簡單的顔色漸變。

構造函數建立一個名為buffer的位元組數組,該數組存儲整個BMP檔案,以頭資訊開頭,後跟像素位。然後,構造函數使用MemoryStream将頭資訊寫入此緩沖區的開頭:

public class BmpMaker
{
    const int headerSize = 54;
    readonly byte[] buffer;
    public BmpMaker(int width, int height)
    {
        Width = width;
        Height = height;
        int numPixels = Width * Height;
        int numPixelBytes = 4 * numPixels;
        int fileSize = headerSize + numPixelBytes;
        buffer = new byte[fileSize];
        // Write headers in MemoryStream and hence the buffer.
        using (MemoryStream memoryStream = new MemoryStream(buffer))
        {
            using (BinaryWriter writer = new BinaryWriter(memoryStream, Encoding.UTF8))
            {
                // Construct BMP header (14 bytes).
                writer.Write(new char[] { 'B', 'M' }); // Signature
                writer.Write(fileSize); // File size
                writer.Write((short)0); // Reserved
                writer.Write((short)0); // Reserved
                writer.Write(headerSize); // Offset to pixels
                // Construct BitmapInfoHeader (40 bytes).
                writer.Write(40); // Header size
                writer.Write(Width); // Pixel width
                writer.Write(Height); // Pixel height
                writer.Write((short)1); // Planes
                writer.Write((short)32); // Bits per pixel
                writer.Write(0); // Compression
                writer.Write(numPixelBytes); // Image size in bytes
                writer.Write(0); // X pixels per meter
                writer.Write(0); // Y pixels per meter
                writer.Write(0); // Number colors in color table
                writer.Write(0); // Important color count
            }
        }
    }
    public int Width
    {
        private set;
        get;
    }
    public int Height
    {
        private set;
        get;
    }
    public void SetPixel(int row, int col, Color color)
    {
        SetPixel(row, col, (int)(255 * color.R), 
                           (int)(255 * color.G), 
                           (int)(255 * color.B), 
                           (int)(255 * color.A));
    }
    public void SetPixel(int row, int col, int r, int g, int b, int a = 255)
    {
        int index = (row * Width + col) * 4 + headerSize;
        buffer[index + 0] = (byte)b;
        buffer[index + 1] = (byte)g;
        buffer[index + 2] = (byte)r;
        buffer[index + 3] = (byte)a;
    }
    public ImageSource Generate()
    {
        // Create MemoryStream from buffer with bitmap.
        MemoryStream memoryStream = new MemoryStream(buffer);
        // Convert to StreamImageSource.
        ImageSource imageSource = ImageSource.FromStream(() =>
        {
            return memoryStream;
        });
        return imageSource;
    }
}           

建立BmpMaker對象後,程式可以調用兩個SetPixel方法之一來設定特定行和列的顔色。 進行非常多次調用時,使用Color值的SetPixel調用明顯慢于接受顯式紅色,綠色和藍色值的調用。

最後一步是調用Generate方法。 此方法基于緩沖區數組執行個體化另一個MemoryStream對象,并使用它來建立FileImageSource對象。 設定新的像素資料後,您可以多次調用Gener?ate。 該方法每次都會建立一個新的MemoryStream,因為ImageSource.FromStream在完成後會關閉Stream對象。

DiyGradientBitmap程式 - “DIY”代表“自己動手” - 示範如何使用BmpMaker制作具有簡單漸變的位圖并顯示它以填充頁面。 XAML檔案包含Image元素:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="DiyGradientBitmap.DiyGradientBitmapPage">
    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness"
                    iOS="0, 20, 0, 0" />
    </ContentPage.Padding>
    <Image x:Name="image"
           Aspect="Fill" />
 
</ContentPage>           

代碼隐藏檔案執行個體化一個BmpMaker并循環通過bit?map的行和列來建立一個漸變,範圍從頂部的紅色到底部的藍色:

public partial class DiyGradientBitmapPage : ContentPage
{
    public DiyGradientBitmapPage()
    {
        InitializeComponent();
        int rows = 128;
        int cols = 64;
        BmpMaker bmpMaker = new BmpMaker(cols, rows);
        for (int row = 0; row < rows; row++)
            for (int col = 0; col < cols; col++)
            {
                bmpMaker.SetPixel(row, col, 2 * row, 0, 2 * (128 - row));
            }
        ImageSource imageSource = bmpMaker.Generate();
        image.Source = imageSource;
    }
}           

這是結果:

第十三章:位圖(七)

現在運用你的想象力,看看你能用BmpMaker做些什麼。

繼續閱讀