问题
After having realized that no realtime graphics effect library exists for MonoTouch, I decided to write my own. After some research I've written a convolution method that works perfectly but, even using unsafe code, is VERY SLOW. What I'm doing wrong? Is there some optimization that I'm missing?
Here is my c# class, any suggestion, not matter how small, is welcome!
using System;
using System.Drawing;
using MonoTouch.CoreGraphics;
using System.Runtime.InteropServices;
using MonoTouch.UIKit;
using MonoTouch;
namespace FilterLibrary
{
public class ConvMatrix
{
public int Factor { get; set; }
public int Offset { get; set; }
private int[,] _matrix = { {0, 0, 0, 0, 0},
{0, 0, 0, 0, 0},
{0, 0, 1, 0, 0},
{0, 0, 0, 0, 0},
{0, 0, 0, 0, 0}
};
public int[,] Matrix
{
get { return _matrix; }
set
{
_matrix = value;
Factor = 0;
for (int i = 0; i < Size; i++)
for (int j = 0; j < Size; j++)
Factor += _matrix[i, j];
if (Factor == 0)
Factor = 1;
}
}
private int _size = 5;
public int Size
{
get { return _size; }
set
{
if (value != 1 && value != 3 && value != 5 && value != 7)
_size = 5;
else
_size = value;
}
}
public ConvMatrix()
{
Offset = 0;
Factor = 1;
}
}
public class ConvolutionFilter
{
public ConvolutionFilter ()
{
}
public static CGImage GaussianSmooth (CGImage image)
{
ConvMatrix matr = new ConvMatrix ();
matr.Matrix = new int[5, 5] {
{ 1 , 4 , 7 , 4 , 1 },
{ 4 ,16 ,26 ,16 , 4 },
{ 7 ,26 ,41 ,26 , 7 },
{ 4 ,16 ,26 ,16 , 4 },
{ 1 , 4 , 7 , 4 , 1 }
};
return Filter.ImageConvolution (image, matr);
}
public static CGImage MotionBlur (CGImage image)
{
ConvMatrix matr = new ConvMatrix ();
matr.Size = 7;
matr.Matrix = new int[7, 7] {
{ 1 , 0 , 0 , 0 , 0 , 0 , 0},
{ 0 , 1 , 0 , 0 , 0 , 0 , 0},
{ 0 , 0 , 1 , 0 , 0 , 0 , 0},
{ 0 , 0 , 0 , 1 , 0 , 0 , 0},
{ 0 , 0 , 0 , 0 , 1 , 0 , 0},
{ 0 , 0 , 0 , 0 , 0 , 1 , 0},
{ 0 , 0 , 0 , 0 , 0 , 0 , 1}
};
return Filter.ImageConvolution (image, matr);
}
public static CGBitmapContext ConvertToBitmapRGBA8 (CGImage imageRef)
{
// Create an empty bitmap context to draw the uiimage into
CGBitmapContext context = NewEmptyBitmapRGBA8ContextFromImage (imageRef);
if (context == null) {
Console.WriteLine ("ERROR: failed to create bitmap context");
return null;
}
RectangleF rect = new RectangleF (0.0f, 0.0f, imageRef.Width, imageRef.Height);
context.ClearRect (rect); //Clear memory area from old garbage
context.DrawImage (rect, imageRef); // Draw image into the context to get the raw image data in our format
return context;
}
public static CGBitmapContext NewEmptyBitmapRGBA8ContextFromImage (CGImage image)
{
CGBitmapContext context = null;
CGColorSpace colorSpace;
IntPtr bitmapData;
int bitsPerComponent = 8; //Forcing only 8 bit formats for now...
int width = image.Width;
int height = image.Height;
int bytesPerRow = image.BytesPerRow;
int bufferLength = bytesPerRow * height;
colorSpace = CGColorSpace.CreateDeviceRGB ();
if (colorSpace == null) {
Console.WriteLine ("Error allocating color space RGB");
return null;
}
// Allocate memory for image data
bitmapData = Marshal.AllocHGlobal (bufferLength);
//Create bitmap context forcing Premultiplied Alpha as required by Apple iOS
if (image.AlphaInfo == CGImageAlphaInfo.PremultipliedFirst || image.AlphaInfo == CGImageAlphaInfo.First) {
context = new CGBitmapContext (bitmapData,
width,
height,
bitsPerComponent,
bytesPerRow,
colorSpace,
CGImageAlphaInfo.PremultipliedFirst); // ARGB
} else {
if (image.AlphaInfo == CGImageAlphaInfo.PremultipliedLast || image.AlphaInfo == CGImageAlphaInfo.Last) {
context = new CGBitmapContext (bitmapData,
width,
height,
bitsPerComponent,
bytesPerRow,
colorSpace,
CGImageAlphaInfo.PremultipliedLast); //RGBA
} else {
Console.WriteLine ("ERROR image format non supported: " + image.AlphaInfo);
throw new Exception ("ERROR image format non supported: " + image.AlphaInfo);
}
}
if (context == null) {
Console.WriteLine ("Bitmap context from BitmapData not created");
}
return context;
}
public static CGImage ImageConvolution (CGImage image, ConvMatrix fmat)
{
//Avoid division by 0
if (fmat.Factor == 0)
return image;
//Create a clone of the original image
CGImage srcImage = image.Clone ();
//init some temporary vars
int x, y, filterx, filtery, tempx, tempy;
int s = fmat.Size / 2;
int a, r, g, b, tr, tg, tb, ta;
int a_div;
float a_mul;
//Compute pixel size (bytes per pixel)
int pixelSize = image.BitsPerPixel / image.BitsPerComponent;
//Create bitmap contexts
CGBitmapContext imageData = ConvertToBitmapRGBA8 (image);
CGBitmapContext srcImageData = ConvertToBitmapRGBA8 (srcImage);
// Scan0 is the memory address where pixel-array begins.
IntPtr scan0 = srcImageData.Data;
// Stride is the width of each row of pixels.
int stride = srcImageData.BytesPerRow;
unsafe {
byte* tempPixel;
for (y = s; y < srcImageData.Height - s; y++) {
for (x = s; x < srcImageData.Width - s; x++) {
a = r = g = b = 0;
a_div = 0;
a_mul = 0.0f;
//Convolution
for (filtery = 0; filtery < fmat.Size; filtery++) {
for (filterx = 0; filterx < fmat.Size; filterx++) {
// Get nearby pixel's position
tempx = x + filterx - s;
tempy = y + filtery - s;
// Go to that pixel in pixel-array
tempPixel = (byte*)scan0 + (tempy * stride) + (tempx * pixelSize);
if (srcImageData.AlphaInfo == CGImageAlphaInfo.First) {
// The format is ARGB (1 byte each).
ta = (int)*tempPixel;
tr = (int)*(tempPixel + 1);
tg = (int)*(tempPixel + 2);
tb = (int)*(tempPixel + 3);
a += fmat.Matrix [filtery, filterx] * ta;
r += fmat.Matrix [filtery, filterx] * (tr);
g += fmat.Matrix [filtery, filterx] * (tg);
b += fmat.Matrix [filtery, filterx] * (tb);
}
if (srcImageData.AlphaInfo == CGImageAlphaInfo.Last) {
// The format is RGBA (1 byte each).
tr = (int)*tempPixel;
tg = (int)*(tempPixel + 1);
tb = (int)*(tempPixel + 2);
ta = (int)*(tempPixel + 3);
a += fmat.Matrix [filtery, filterx] * ta;
r += fmat.Matrix [filtery, filterx] * (tr);
g += fmat.Matrix [filtery, filterx] * (tg);
b += fmat.Matrix [filtery, filterx] * (tb);
}
if (srcImageData.AlphaInfo == CGImageAlphaInfo.PremultipliedFirst) {
// The format is premultiplied ARGB (1 byte each).
ta = (int)*tempPixel;
tr = (int)*(tempPixel + 1);
tg = (int)*(tempPixel + 2);
tb = (int)*(tempPixel + 3);
// Computing alpha
a += fmat.Matrix [filtery, filterx] * ta;
a_div = (ta / 255);
// Computing rgb
if (a_div == 0) {
r += fmat.Matrix [filtery, filterx] * (tr);
g += fmat.Matrix [filtery, filterx] * (tg);
b += fmat.Matrix [filtery, filterx] * (tb);
} else {
r += fmat.Matrix [filtery, filterx] * (tr / a_div); // "Dividing the premultiplied value by the
g += fmat.Matrix [filtery, filterx] * (tg / a_div); // alpha value to get the original color
b += fmat.Matrix [filtery, filterx] * (tb / a_div); // value before matrix multiplication"
}
}
if (srcImageData.AlphaInfo == CGImageAlphaInfo.PremultipliedLast) {
// The format is premultiplied RGBA (1 byte each). Get em
tr = (int)*tempPixel;
tg = (int)*(tempPixel + 1);
tb = (int)*(tempPixel + 2);
ta = (int)*(tempPixel + 3);
// Computing alpha
a += fmat.Matrix [filtery, filterx] * ta;
a_div = (ta / 255);
// Computing rgb
if (a_div == 0) {
r += fmat.Matrix [filtery, filterx] * (tr);
g += fmat.Matrix [filtery, filterx] * (tg);
b += fmat.Matrix [filtery, filterx] * (tb);
} else {
r += fmat.Matrix [filtery, filterx] * (tr / a_div); // "Dividing the premultiplied value by the
g += fmat.Matrix [filtery, filterx] * (tg / a_div); // alpha value to get the original color
b += fmat.Matrix [filtery, filterx] * (tb / a_div); // value before matrix multiplication"
}
}
}
}
// Remove values out of [0,255]
a = Math.Min (Math.Max ((a / fmat.Factor) + fmat.Offset, 0), 255);
r = Math.Min (Math.Max ((r / fmat.Factor) + fmat.Offset, 0), 255);
g = Math.Min (Math.Max ((g / fmat.Factor) + fmat.Offset, 0), 255);
b = Math.Min (Math.Max ((b / fmat.Factor) + fmat.Offset, 0), 255);
// Premultiplying color value by alpha value if needed by image format
if (srcImageData.AlphaInfo == CGImageAlphaInfo.PremultipliedFirst || srcImageData.AlphaInfo == CGImageAlphaInfo.PremultipliedLast) {
a_mul = (a / 255.0f);
r = (int)(r * a_mul);
g = (int)(g * a_mul);
b = (int)(b * a_mul);
}
// Finally compute new pixel position (in new image) and write the pixels.
if (srcImageData.AlphaInfo == CGImageAlphaInfo.PremultipliedFirst || srcImageData.AlphaInfo == CGImageAlphaInfo.First) {
// The format is ARGB (1 byte each)
byte* newpixel = (byte*)imageData.Data + (y * imageData.BytesPerRow) + (x * pixelSize);
*newpixel = (byte)a;
*(newpixel + 1) = (byte)r;
*(newpixel + 2) = (byte)g;
*(newpixel + 3) = (byte)b;
}
if (srcImageData.AlphaInfo == CGImageAlphaInfo.PremultipliedLast || srcImageData.AlphaInfo == CGImageAlphaInfo.Last) {
// The format is RGBA (1 byte each)
byte* newpixel = (byte*)imageData.Data + (y * imageData.BytesPerRow) + (x * pixelSize);
*newpixel = (byte)r;
*(newpixel + 1) = (byte)g;
*(newpixel + 2) = (byte)b;
*(newpixel + 3) = (byte)a;
}
}
}
}
return imageData.ToImage ();
}
}
}
回答1:
Well, there's lots that can be done to improve that code, like moving all those decisions out of the main loop; do them outside, use them to provide a method for that loop to call.
However, the main thing would be to find out if you can wrap Core Image, which ought to be very fast indeed, as it will do it using shaders on the GPU.
来源:https://stackoverflow.com/questions/7625582/how-to-optimize-this-image-convolution-filter-method-in-monotouch