2017-01-05 5 views
3

Итак, вот какой-то контекст. Я работаю над этой игрой под названием ShiftOS, которая происходит в ОС, которая запускается как голой запуск операционной системы мельницы 80 с небольшим количеством функций.Мой алгоритм сглаживания очень медленный

Я пытаюсь добавить механика, где пользователь должен начать с двоичной (двухцветной) глубины цвета и может отображать только черно-белое изображение на экране. Затем они должны обновить глубину цвета от 1 до 2 бит до 4 бит до 24-бит. Это действительно опрятный механик, но на практике это кажется чрезвычайно сложным.

Конечно, более старые системы примерно в это время сделали, по крайней мере, TRY, чтобы изображения выглядели красиво, но, конечно, они были ограничены цветовыми палитрами, заданными инженерами, поэтому им пришлось сгладить изображения, чтобы упорядочить пиксели таким образом, чтобы сделало его похожим на то, что изображение использовало больше цветов, когда во всей реальности он мог использовать только 2.

Итак, я искал хорошие алгоритмы сглаживания и начал изучать алгоритм Флойда-Штайнберга и вскоре перенес его на C# и System.Drawing.

Вот код, который я использую.

var bmp = new Bitmap(source.Width, source.Height); 
var sourceBmp = (Bitmap)source; 
int error = 0; 
for (int y = 0; y < bmp.Height; y++) 
{ 
    for (int x = 0; x < bmp.Width; x++) 
    { 
     Color c = sourceBmp.GetPixel(x, y); 
     int gray = ((c.R + c.G + c.B)/3); 
     if (gray >= 127) 
     { 
      error = gray - 255; 
      bmp.SetPixel(x, y, Color.White); 
     } 
     else 
     { 
      error = gray; 
      bmp.SetPixel(x, y, Color.Black); 
     } 
     /* 
     * Pixel error diffusion map: Floyd-Steinberg. Thanks to Wikipedia. 
     * 
     * pixel[x + 1][y ] := pixel[x + 1][y ] + quant_error * 7/16 
     * pixel[x - 1][y + 1] := pixel[x - 1][y + 1] + quant_error * 3/16 
     * pixel[x ][y + 1] := pixel[x ][y + 1] + quant_error * 5/16 
     * pixel[x + 1][y + 1] := pixel[x + 1][y + 1] + quant_error * 1/16 
     */ 

     if(x - 1 >= 0 && y + 1 != bmp.Height) 
     { 
      var bottomRightColor = sourceBmp.GetPixel(x - 1, y + 1); 
      int bottomRightGray = ((bottomRightColor.R + bottomRightColor.G + bottomRightColor.B)/3) + ((error * 3)/16); 
      if (bottomRightGray < 0) 
       bottomRightGray = 0; 
      if (bottomRightGray > 255) 
       bottomRightGray = 255; 
      sourceBmp.SetPixel(x - 1, y + 1, Color.FromArgb(bottomRightGray, bottomRightGray, bottomRightGray)); 
     } 
     if (x + 1 != sourceBmp.Width) 
     { 
      var rightColor = sourceBmp.GetPixel(x + 1, y); 
      int rightGray = ((rightColor.R + rightColor.G + rightColor.B)/3) + ((error * 7)/16); 
      if (rightGray < 0) 
       rightGray = 0; 
      if (rightGray > 255) 
       rightGray = 255; 
      sourceBmp.SetPixel(x + 1, y, Color.FromArgb(rightGray, rightGray, rightGray)); 
     } 
     if (x + 1 != sourceBmp.Width && y + 1 != sourceBmp.Height) 
     { 
      var bottomRightColor = sourceBmp.GetPixel(x + 1, y + 1); 
      int bottomRightGray = ((bottomRightColor.R + bottomRightColor.G + bottomRightColor.B)/3) + ((error)/16); 
      if (bottomRightGray < 0) 
       bottomRightGray = 0; 
      if (bottomRightGray > 255) 
       bottomRightGray = 255; 
      sourceBmp.SetPixel(x + 1, y + 1, Color.FromArgb(bottomRightGray, bottomRightGray, bottomRightGray)); 
     } 
     if (y + 1 != sourceBmp.Height) 
     { 
      var bottomColor = sourceBmp.GetPixel(x, y + 1); 
      int bottomGray = ((bottomColor.R + bottomColor.G + bottomColor.B)/3) + ((error * 5)/16); 
      if (bottomGray < 0) 
       bottomGray = 0; 
      if (bottomGray > 255) 
       bottomGray = 255; 
      sourceBmp.SetPixel(x, y + 1, Color.FromArgb(bottomGray, bottomGray, bottomGray)); 
     } 
    } 
} 

Обратите внимание, что source является Image, который передается через функцию с помощью аргумента.

Этот код работает очень хорошо, однако проблема заключается в том, что сглаживание происходит в отдельном потоке, чтобы минимизировать замедление/отставание в игре, и в то время как происходит сглаживание, обычные 24-битные цвета/изображения операционной системы. Это было бы хорошо, если бы сглаживание не заходило так долго.

Однако я замечаю, что этот алгоритм ЧРЕЗВЫЧАЙНО медленный в этом коде и в зависимости от размера изображения, которое я сглаживаю, процесс сглаживания может занять дольше минуты!

Я применил все оптимизации, о которых я могу думать, - например, запускать вещи в отдельном потоке из игрового потока и вызывать действие, которое передается функции, когда поток заканчивается, но это только сокращает крошечный бит времени, если Любые.

Так что мне интересно, есть ли какие-либо дальнейшие оптимизации, чтобы это работало быстрее, на несколько секунд, если возможно. Я также хотел бы отметить, что, когда происходит операция сглаживания, у меня наблюдается заметное отставание системы - мышь даже колеблется и время от времени прыгает. Не круто для тех, кто должен иметь 60FPS.

+0

Честно говоря, я не очень уверен в том, что делает ваш алгоритм .. но я могу понять, что на каждой итерации вы устанавливаете цвет на четыре точки (внизу слева, справа, внизу справа и внизу) на источнике image - зачем вам нужно четыре? не повторяется ли он слишком много раз - вы каждый раз делаете 4 раза в буквальном смысле. Нельзя ли установить его только для каждой точки? и даже больше, вы можете использовать многопоточный набор различных диапазонов, если вы уверены, что цвет одной точки не зависит от другого. – Rex

+4

'GetPixel' и' SetPixel' чрезвычайно медленны. Честно говоря, эти функции даже не существуют, для них нет реалистичного варианта использования - для функций с их семантикой, конечно, но с реализацией, что они у них хуже, чем бесполезно - они ловушки. – harold

+3

Доступ к пикселям растрового изображения * всегда * требует использования LockBits(). Это функция удержания дома, она обеспечивает доступность и актуальность отображения данных пикселей в памяти. В большинстве случаев вы никогда не видите, что он используется, например, когда вы используете Graphics.DrawImage(). Но когда вы используете Get/SetPixel(), тогда, конечно, вы заметите его накладные расходы. O (n^2) является уродливым числом. Вот почему LockBits() напрямую используется, что нужно для быстрого создания этого кода. –

ответ

0

Первое, что приходит мне в голову - это дело с Bitmap, так как это будет массив. По умолчанию это не вариант, поскольку для этого нет интерфейса, но вы можете с некоторыми хаками достичь этого. Быстрый поиск следовал за мной до this answer. Таким образом, вы должны установить свой метод как unsafe, получить значения пикселей с LockBits, и доступ к ним с указателем математики (см оригинальный ответ на полный код):

System.Drawing.Imaging.BitmapData bmpData = 
    bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite, 
    bmp.PixelFormat); 
var pt = (byte*)bmpData.Scan0; 
// for loop 
var row = pt + (y * bmpData.Stride); 
var pixel = row + x * bpp; // bpp is a number of dimensions for the bitmap 

pixel будет массив с информацией о цвета, закодированные в значениях byte. Как вы уже видели, GetPixel и SetPixel являются медленными, потому что они фактически вызывают LockBits для обеспечения работы. Массив поможет вам удалить операции чтения, однако «SetPixel» по-прежнему может быть узким местом, поскольку вам может потребоваться обновить растровое изображение как можно быстрее. Если вы можете обновить его в конце и сразу, то сделайте это.

Вторая мысль заключается в создании очереди Task, которая будет обновлять ваш массив шаг за шагом. Как я вижу, вы обновляете свое изображение с одного угла, так что, возможно, вы можете настроить параллельную версию своего обновления. Возможно, вы можете создать неизменяемый массив текущего состояния с управлением версиями, поэтому в итоге вы просто подытоживаете новую версию bmp.

Смежные вопросы