10.5 使用wxImage编程

你可以使用wxImage对图形进行一些平台无关的调整,或者将其作为图片加载和保存的中间步骤.图片在wxImage中是按照每一个象素使用一个分别代表红色,绿色和蓝色的字节的格式保存的,如果图片包含alpha通道,则还会占用额外的一个字节.

wxImage主要的函数如下:

wxImage wxImage的创建方法包括:指定宽度和高度, 从另外一幅图片创建, 使用XPM数据, 图片元数据(char[]) 和可选的alpha通道数据,文件名及其类型,以及通过输入流等多种方式创建.
ConvertAlphaToMask 将alpla通道(如果有的话)转换成一个透明遮罩.
ConvertToMono 转换成一个黑白图片.
Copy 返回一个不使用引用记数器的完全一样的拷贝.
Create 创建一个指定大小的图片,可选的参数指明是否初始化图片数据.
Destroy 如果没有人再使用的话,释放内部数据.
GeTData, SetData 获取和设置内部数据指针(unsigned char*).
GetImageCount 返回一个文件或者流中的图片个数.
GetOption, GetOptionInt, SetOption, HasOption 获取, 设置和测试某个选项是否设置.
GetSubImage 将图片的一部分返回为一个新的图像.
GetWidth, GetHeight 返回图片大小.
Getred, GetGreen, GetBlue, SetRGB, GetAlpha, SetAlpha 获得和指定某个象素的RGB以及Alpha通道的值.
HasMask, GetMaskRed, GetMaskGreen, GetMaskBlue, SetMaskColour 用来测试图像是否有一个遮罩,以及遮罩颜色的RGB值或者整个颜色的值.
LoadFile, SaveFile 各种图片格式文件的读取和保存操作.
Mirror 在各种方向上产生镜像,返回一个新图片.
Ok 判断图片是否已初始化.
Paste 将某个图片粘贴在这个图片的指定位置.
Rotate, Rotate90 旋转图片,返回一个新图片.
SetMaskFromImage 通过指定的图片和透明颜色产生一个遮罩并且设置这个遮罩.
Scale, Rescale 缩放产生一个新图片或者缩放本图片.

加载和保存图像

wxImage可以读取和保存各种各样的图片格式,并且使用图像处理过程来增加扩展的能力.其它的图像类(比如wxBitmap)在某个平台不具备处理某种图形格式的能力的时候,也通常使用的都是wxImage的图象处理过程来加载特定格式的图形.

本章第二小节中展示了wxWidgets支持的各种图形处理过程.其中wxBMPHandler是默认支持的,而要支持其它的图形格式处理,就需要使用 wxImage::AddHandler函数增加对应的图形处理过程或者使用wxInitAllImageHandlers增加所有支持的图形处理过程.

如果你只需要特定的图形格式支持,可以在OnInit函数中使用类似下面的代码:

#include "wx/image.h"
wxImage::AddHandler( new wxPNGHandler );
wxImage::AddHandler( new wxJPEGHandler );
wxImage::AddHandler( new wxGIFHandler );
wxImage::AddHandler( new wxXPMHandler );

或者,你可以简单的调用:

wxInitAllImageHandlers();

下面演示了几种从文件或者流读取图片的方式,注意在实际使用过程中,最好使用绝对路径以避免依赖于当前路径的设置:

// 使用构造函数指定类型来读取图像
wxImage image(wxT("image.png"), wxBITMAP_TYPE_PNG);
if (image.Ok())
{
    ...
}
// 不指定图像类型一般也能正常工作
wxImage image(wxT("image.png"));
// 使用两步法创建图像
wxImage image;
if (image.LoadFile(wxT("image.png")))
{
    ...
}
// 如果一个文件包含两副图片Two-step loading with an index into a multi-image file:
// 下面演示选择第2副加载
wxImage image;
int imageCount = wxImage::GetImageCount(wxT("image.tif"));
if (imageCount > 2)
    image.LoadFile(wxT("image.tif"), wxBITMAP_TYPE_TIFF, 2);
// 从文件流加载图片
wxFileInputStream stream(wxT("image.tif"));
wxImage image;
image.LoadFile(stream, wxBITMAP_TYPE_TIF);
// 保存到一个文件
image.SaveFile(wxT("image.png")), wxBITMAP_TYPE_PNG);
// 保存到一个流
wxFileOutputStream stream(wxT("image.tif"));
image.SaveFile(stream, wxBITMAP_TYPE_TIF);

除了XPM和PCX格式以外,其它的图片格式都将以24位颜色深度保存(译者注:GIF格式因为版权方面的原因不支持保存到文件),这两种格式的图形处理过程将会计算实际的颜色个数从而选择相应的颜色深度.JPEG格式还拥有一个质量选项可供设置.它的值的范围为从0到100,0代表最低的图片质量和最高的压缩比,100则代表最高的图片质量和最低的压缩比.如下所示:

// 设置一个合理的质量压缩比
image.SetOption(wxIMAGE_OPTION_QUALITY, 80);
image.SaveFile(wxT("picture.jpg"), wxBITMAP_TYPE_JPEG);

另外如果以XPM格式保存到流输出中的时候,需要使用wxImage::SetOption函数设置一个名称否则,处理函数不知道该用什么名称命名对应的C变量.

// 保存XPM到流格式
image.SetOption(wxIMAGE_OPTION_FILENAME, wxT("myimage"));
image.SaveFile(stream, wxBITMAP_TYPE_XPM);

注意处理函数会自动在你设置的名称后增加"_xpm".

透明

有两种方式设置一个wxImage为透明的图像:使用颜色遮罩或者alpha通道.一种颜色可以被指定为透明颜色,通过这种方法在将wxImage转换成wxBitmap的时候可以很容易的制作一个透明遮罩.

wxImage也支持alpha通道数据,在每一个象素的RGB颜色之外来由另外一个字节用来指示alpha通道的值,0代表完全透明,255则代表完全不透明.中间的值代表半透明.

不是所有的图片都用有alpha通道数据的,因此在使用GetAlpha函数之前,应该使用HasAlpha函数来判断图像是否拥有 alpha通道数据.到目前为止,只有PNG文件或者调用SetAlpha设置了alpha通道的图像才拥有alpha通道数据.保存一个带有alpha 通道的图像目前还不被支持.绘制一个拥有alpha通道的方法是先将其转换成wxBitmap然后使用wxDC::DrawBitmap或者wxDC:: Blit函数.

下面的代码演示了怎样使用颜色掩码创建一个透明的wxImage,它是蓝色的,拥有一个透明的矩形区域:

// 创建一个有颜色掩码的wxBitmap
// 首先,在这个wxBitmap上绘画
wxBitmap bitmap(400, 400);
wxMemoryDC dc;
dc.SelectObject(bitmap);
dc.SetBackground(*wxBLUE_BRUSH);
dc.Clear();
dc.SetPen(*wxRED_PEN);
dc.SetBrush(*wxRED_BRUSH);
dc.DrawRectangle(50, 50, 200, 200);
dc.SelectObject(wxNullBitmap);
// 将其转换成wxImage
wxImage image = bitmap.ConvertToImage();
// 设置掩码颜色
image.SetMaskColour(255, 0, 0);

在下面的例子中,使用从一个图片创建颜色遮罩的方式,其中image.bmp是原始图像,而mask.bmp则是一个掩码图像,在后者中所有透明的部分都是黑色显示的.

// 加载一副图片和它的掩码遮罩
wxImage image(wxT("image.bmp"), wxBITMAP_TYPE_BMP);
wxImage maskImage(wxT("mask.bmp"), wxBITMAP_TYPE_BMP);
// 从后者创建一个遮罩并且设置给前者.
image.SetMaskFromImage(maskImage, 0, 0, 0);

如果你加载的图片本身含有透明颜色,你可以检测并且直接创建遮罩:

// 加载透明图片
wxImage image(wxT("image.png"), wxBITMAP_TYPE_PNG);
// 获取掩码
if (image.HasMask())
{
    wxColour maskColour(image.GetMaskRed(),
    image.GetMaskGreen(),
    image.GetMaskBlue());
}

变形

wxImage支持缩放,旋转以及镜像等多种变形方式,下面各举一些例子:

// 把原始图片缩放到200x200,并保存在新的图片里
// 原图保持不变.
wxImage image2 = image1.Scale(200, 200);
// 将原图缩放到200x200
image1.Rescale(200, 200);
// 旋转固定角度产生新图片.
// 原图片保持不变.
wxImage image2 = image1.Rotate(0.5);
// 顺时针旋转90度产生新图片.
// 原图保持不变.
wxImage image2 = image1.Rotate90(true);
// 水平镜像产生新图片.
// 原图保持不变.
wxImage image2 = image1.Mirror(true);

颜色消减

如果你想对某个图像的颜色进行消减,你可以使用wxQuantize类的一些静态函数,其中最有趣的函数Quantize的参数为一个输入图片,一个输出图片,一个可选的wxPalette指针用来存放经过消减的颜色,以及一个你希望保留的颜色个数,你也可以传递一个unsigned char变量来获取一个8-bit颜色深度的输出图像.最后的一个参数style(类型)用来对返回的图像进行一些更深入的控制,详情请参考 wxWidgets的手册.

下面的代码演示了怎样将一幅图片的颜色消减到最多256色:

#include "wx/image.h"
#include "wx/quantize.h"
wxImage image(wxT("image.png"));
int maxColorCount = 256;
int colors = image.CountColours();
wxPalette* palette = NULL;
if (colors > maxColorCount )
{
    wxImage reducedImage;
    if (wxQuantize::Quantize(image, reducedImage,
                               & palette, maxColorCount))
    {
        colors = reducedImage.CountColours();
        image = reducedImage;
    }
}

一个wxImage可以设置一个wxPalette,例如加载GIF文件的时候. 然后,图片内部仍然是以RGB的方式存储数据的,调色板仅代表图片加载时候的颜色隐射关系.调色板的另外一个用途是某些图片处理函数用它来将图片保存为低颜色深度的图片,例如windows的BMP图片处理过程将检测是否设置了wxBMP_8BPP_PALETTE标记,如果设置了,则将使用调色板.而如果设置了wxBMP_8BPP标记(而不是wxBMP_8BPP_PALETTE),它将使用自己的算法进行颜色消减.另外某些图片处理过程自己也进行颜色消减,比如PCX的处理过程,除非它认为剩余的颜色个数已经足够低了,否则它将对图片的颜色进行消减.

关于调色板更多的信息请参考第5章的"调色板"小节.

直接操作wxImage 的元数据

你可以直接通过GetData函数访问wxImage的元数据以便以比GeTRed, GetBlue, GetGreen和SetRGB更快的方式对其进行操作,下面举了一个使用这种方法将一个图片转换成灰度图片的方法:

void wxImage::ConvertToGrayScale(wxImage& image)
{
    double red2Gray   = 0.297;
    double green2Gray = 0.589;
    double blue2Gray  = 0.114;
    int w = image.GetWidth(), h = image.GetHeight();
    unsigned char *data = image.GetData();
    int x,y;
    for (y = 0; y < h; y++)
        for (x = 0; x < w; x++)
        {
            long pos = (y * w + x) * 3;
            char g = (char) (data[pos]*red2Gray +
                              data[pos+1]*green2Gray +
                              data[pos+2]*blue2Gray);
            data[pos] = data[pos+1] = data[pos+2] = g;
        }
}