Сначала я хочу представить вам некоторый контекст.
У меня есть два вида изображений, которые мне нужно объединить. Первое изображение — это фоновое изображение с форматом 8BppGrey и разрешением 320х240. Второе изображение — переднее изображение в формате 32BppRGBA и разрешением 64х48.
Обновить
Github репо с MVP находится в нижней части вопроса.
Для этого я изменяю размер второго изображения с помощью билинейной интерполяции до того же размера, что и первое, а затем использую смешивание, чтобы объединить оба изображения. Смешивание происходит только тогда, когда значение альфа второго изображения больше 0.
Мне нужно сделать это как можно быстрее, поэтому моя идея заключалась в том, чтобы объединить процесс изменения размера и слияния / наложения.
Для этого я использовал функцию изменения размера из репозиторий writeablebitmapex и добавил слияние / смешивание.
Все работает как положено, но я хочу уменьшить время выполнения.
Это текущие сроки отладки:
// CPU: Intel(R) Core(TM) i7-4810MQ CPU @ 2.80GHz
MediaServer: Execution time in c++ 5 ms
MediaServer: Resizing took 4 ms.
MediaServer: Execution time in c++ 5 ms
MediaServer: Resizing took 5 ms.
MediaServer: Execution time in c++ 4 ms
MediaServer: Resizing took 4 ms.
MediaServer: Execution time in c++ 3 ms
MediaServer: Resizing took 3 ms.
MediaServer: Execution time in c++ 4 ms
MediaServer: Resizing took 4 ms.
MediaServer: Execution time in c++ 5 ms
MediaServer: Resizing took 4 ms.
MediaServer: Execution time in c++ 6 ms
MediaServer: Resizing took 6 ms.
MediaServer: Execution time in c++ 3 ms
MediaServer: Resizing took 3 ms.
Есть ли у меня шанс повысить производительность и уменьшить время выполнения процесса изменения размера / слияния / наложения?
Есть ли некоторые части, которые я могу распараллелить?
Могу ли я использовать некоторые функции процессора?
Огромный удар по производительности — это вложенный цикл, но я понятия не имею, как мне написать его лучше.
Я хотел бы достичь 1 или 2 мс для всего процесса. Это вообще возможно?
Вот модифицированная визуальная функция C ++, которую я использую.
Код VC ++:
unsigned int Resize(int* pd, int* pixels, int widthSource, int heightSource, int width, int height, byte* baseImage)
{
unsigned int start = clock();
float xs = (float)widthSource / width;
float ys = (float)heightSource / height;
float fracx, fracy, ifracx, ifracy, sx, sy, l0, l1, rf, gf, bf;
int c, x0, x1, y0, y1;
byte c1a, c1r, c1g, c1b, c2a, c2r, c2g, c2b, c3a, c3r, c3g, c3b, c4a, c4r, c4g, c4b;
byte a, r, g, b;
// Bilinear
int srcIdx = 0;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
sx = x * xs;
sy = y * ys;
x0 = (int)sx;
y0 = (int)sy;
// Calculate coordinates of the 4 interpolation points
fracx = sx - x0;
fracy = sy - y0;
ifracx = 1.0f - fracx;
ifracy = 1.0f - fracy;
x1 = x0 + 1;
if (x1 >= widthSource)
{
x1 = x0;
}
y1 = y0 + 1;
if (y1 >= heightSource)
{
y1 = y0;
}
// Read source color
c = pixels[y0 * widthSource + x0];
c1a = (byte)(c >> 24);
c1r = (byte)(c >> 16);
c1g = (byte)(c >> 8);
c1b = (byte)(c);
c = pixels[y0 * widthSource + x1];
c2a = (byte)(c >> 24);
c2r = (byte)(c >> 16);
c2g = (byte)(c >> 8);
c2b = (byte)(c);
c = pixels[y1 * widthSource + x0];
c3a = (byte)(c >> 24);
c3r = (byte)(c >> 16);
c3g = (byte)(c >> 8);
c3b = (byte)(c);
c = pixels[y1 * widthSource + x1];
c4a = (byte)(c >> 24);
c4r = (byte)(c >> 16);
c4g = (byte)(c >> 8);
c4b = (byte)(c);
// Calculate colors
// Alpha
l0 = ifracx * c1a + fracx * c2a;
l1 = ifracx * c3a + fracx * c4a;
a = (byte)(ifracy * l0 + fracy * l1);
// Write destination
if (a > 0)
{
// Red
l0 = ifracx * c1r + fracx * c2r;
l1 = ifracx * c3r + fracx * c4r;
rf = ifracy * l0 + fracy * l1;
// Green
l0 = ifracx * c1g + fracx * c2g;
l1 = ifracx * c3g + fracx * c4g;
gf = ifracy * l0 + fracy * l1;
// Blue
l0 = ifracx * c1b + fracx * c2b;
l1 = ifracx * c3b + fracx * c4b;
bf = ifracy * l0 + fracy * l1;
// Cast to byte
float alpha = a / 255.0f;
r = (byte)((rf * alpha) + (baseImage[srcIdx] * (1.0f - alpha)));
g = (byte)((gf * alpha) + (baseImage[srcIdx] * (1.0f - alpha)));
b = (byte)((bf * alpha) + (baseImage[srcIdx] * (1.0f - alpha)));
pd[srcIdx++] = (255 << 24) | (r << 16) | (g << 8) | b;
}
else
{
// Alpha, Red, Green, Blue
pd[srcIdx++] = (255 << 24) | (baseImage[srcIdx] << 16) | (baseImage[srcIdx] << 8) | baseImage[srcIdx];
}
}
}
unsigned int end = clock() - start;
return end;
}
Одним действием, которое может ускорить ваш код, является недопущение преобразования типов из целого числа в число с плавающей точкой и наоборот. Это может быть достигнуто с помощью значения int в подходящем диапазоне вместо значений с плавающей запятой в диапазоне 0..1
Что-то вроде этого:
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
int sx1 = x * widthSource ;
int x0 = sx1 / width;
int fracx = (sx1 % width) ; // range 0..width - 1
который превращается в нечто вроде
l0 = (fracx * c2a + (width - fracx) * c1a) / width ;
И так далее. Немного сложно, но выполнимо
Спасибо за помощь, но проблема заключалась в управляемом проекте c ++. Теперь я перенес эту функцию в свою родную библиотеку c ++ и использовал управляемую часть c ++ только в качестве оболочки для приложения c #.
После оптимизации компилятора функция завершается за 1 мс.
Редактировать:
Я пока обозначу свой ответ как решение, потому что оптимизация от @marom приводит к повреждению изображения.
Обычный способ ускорить операцию изменения размера с билинейной интерполяцией заключается в следующем:
Используйте тот факт, что x0
а также fracx
не зависят от ряда и y0
а также fracy
не зависят от столбца. Даже если вы не вытащили вычисление y0
а также fracy
вне х-цикла об этом должна позаботиться оптимизация компилятора. Однако для x0
а также fracx
необходимо предварительно вычислить значения для всех столбцов и сохранить их в массиве. Сложность для вычислений x0
а также fracx
становится O (ширина) по сравнению с O (ширина * высота) без предварительного вычисления.
Выполните всю обработку с целыми числами, заменив арифметику с плавающей запятой на целочисленную арифметику, тем самым используя операции сдвига вместо целочисленных делений.
Для лучшей читабельности я не реализовал предварительное вычисление x0
а также fracx
в следующем коде. Предварительные вычисления в любом случае просты.
Обратите внимание, что FACTOR = 2048
это максимум, который вы можете сделать с 32-битными знаковыми целыми числами здесь (2048 * 2048 * 255 — это нормально). Для более высокой точности следует переключиться на int64_t
и затем увеличьте ФАКТОР и СДВИГ, соответственно.
Я поместил проверку границы во внутренний цикл для лучшей читаемости. Для оптимизированной реализации нужно удалить ее, выполнив итерации в обоих циклах непосредственно перед тем, как это произойдет, и добавить специальную обработку для пикселей границы.
Если кому-то интересно, что + (FACTOR * FACTOR / 2)
для, это для округления в сочетании с последующим делением.
Наконец, обратите внимание, что (FACTOR * FACTOR / 2)
а также 2 * SHIFT
оцениваются во время компиляции.
#define FACTOR 2048
#define SHIFT 11
const int xs = (int) ((double) FACTOR * widthSource / width + 0.5);
const int ys = (int) ((double) FACTOR * heightSource / height + 0.5);
for (int y = 0; y < height; y++)
{
const int sy = y * ys;
const int y0 = sy >> SHIFT;
const int fracy = sy - (y0 << SHIFT);
for (int x = 0; x < width; x++)
{
const int sx = x * xs;
const int x0 = sx >> SHIFT;
const int fracx = sx - (x0 << SHIFT);
if (x0 >= widthSource - 1 || y0 >= heightSource - 1)
{
// insert special handling here
continue;
}
const int offset = y0 * widthSource + x0;
target[y * width + x] = (unsigned char)
((source[offset] * (FACTOR - fracx) * (FACTOR - fracy) +
source[offset + 1] * fracx * (FACTOR - fracy) +
source[offset + widthSource] * (FACTOR - fracx) * fracy +
source[offset + widthSource + 1] * fracx * fracy +
(FACTOR * FACTOR / 2)) >> (2 * SHIFT));
}
}
Для пояснения, чтобы соответствовать переменным, используемым OP, например, в случае альфа-канала это:
a = (unsigned char)
((c1a * (FACTOR - fracx) * (FACTOR - fracy) +
c2a * fracx * (FACTOR - fracy) +
c3a * (FACTOR - fracx) * fracy +
c4a * fracx * fracy +
(FACTOR * FACTOR / 2)) >> (2 * SHIFT));