Повышение скорости отрисовки Bitmap с масштабированием в WinForms
Нужен совет по оптимизации. Есть винформа, в которой отображается картинка. Картинка хранится в памяти как Bitmap. Картинка используется для отображения карты (поверхности), в которой в пределах прямоугольной сетки размерами NX на NY для каждого узла сетки задано числовое значение Z, и разные значения Z отображаются разными цветами. Количество пикселей битмапа равно количеству узлов сетки. При увеличении масштаба, цвета просто интерполируются между узлами. Можно увеличивать или уменьшать масштаб формы, а также менять размер самой формы с соответствующим изменением размера картинки. Пытался сделать отрисовку битмапа через стандартный класс System.Drawing.Graphics. Проблема в том, что он очень медленно работает при масштабировании картинки. Отрисовка картинки с масштабированием на весь экран занимает 250-350 мс на 4к мониторе на моём компьютере. Кроме того, правый столбец и нижняя строка пикселей интерполируется Graphics'ом криво (цвет смешивается с фоновым цветом). В качестве эксперимента написал реализацию масштабирования битмапа (с билинейной интерполяцией) с использованием unsafe, векторами и параллелизмом. На вход подаем указатель на данные первого пикселя исходного битмапа и его размеры и также указатель целевого битмапа и его размеры. Указатель получаем через метод Bitmap.LockBits().
public static void Rescale_bmp_parallel_optimised2(
IntPtr SrcPtr, int SrcNx, int SrcNy,
IntPtr DstPtr, int DstNx, int DstNy)
{
int* ᴬSrcClr = (int*)SrcPtr;
int* ᴬDstClr = (int*)DstPtr;
int SrcN = SrcNx * SrcNy;
Vector3[] ᴬOldClrVec3 = new Vector3[SrcN];
ParallelOptions po = new();
po.MaxDegreeOfParallelism = Environment.ProcessorCount;
Action<int> col_to_vec = (int i) =>
{
int color = ᴬSrcClr[i];
float R = (color & 0x0000FF);
float G = ((color & 0x00FF00) >> 8);
float B = ((color & 0xFF0000) >> 16);
ᴬOldClrVec3[i] = new Vector3(R, G, B);
};
Parallel.For(0, SrcN, po, col_to_vec);
int DstN = DstNx * DstNy;
float kx = (float)(SrcNx - 2) / (DstNx - 1);
float ky = (float)(SrcNy - 2) / (DstNy - 1);
if (true)
{
Action<int> interp = (int i) =>
{
int iynew = Math.DivRem(i, DstNx, out int ixnew);
float ixoldf = ixnew * kx;
float iyoldf = iynew * ky;
int ix1 = (int)(ixoldf);
int iy1 = (int)(iyoldf);
//if (ix1 > SrcNx - 2) ix1 = SrcNx - 2;
//if (iy1 > SrcNy - 2) iy1 = SrcNy - 2;
int i11 = iy1 * SrcNx + ix1;
int i12 = (iy1 + 1) * SrcNx + ix1;
int i21 = i11 + 1;
int i22 = i12 + 1;
Vector3 z11 = ᴬOldClrVec3[i11];
Vector3 z12 = ᴬOldClrVec3[i12];
Vector3 z21 = ᴬOldClrVec3[i21];
Vector3 z22 = ᴬOldClrVec3[i22];
Vector3 z_on_y1_edge = Vector3.Lerp(z11, z21, ixoldf - ix1);
Vector3 z_on_y2_edge = Vector3.Lerp(z12, z22, ixoldf - ix1);
Vector3 z = Vector3.Lerp(z_on_y1_edge, z_on_y2_edge, iyoldf - iy1);
ᴬDstClr[i] =
(byte)(z.X)
| (byte)(z.Y) << 8
| (byte)(z.Z) << 16;
};
Parallel.For(0, DstN, po, interp);
}
}
Эта реализация работает в 3-4 раза быстрее (80-90 мс на отрисовку), что уже терпимо, но всё равно долго.
Можно ли сделать это за 10-20 мс без сторонних либ-обёрток вокруг DirectX и без WFP?
Либо же может быть что-то похожее на приложенную ниже картинку можно нарисовать не через Bitmap?
