在运行时生成位图
所有这三个平台都支持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做些什么。