Как использовать свойство ScanLine для 24-битных растровых изображений?

30

Как использовать ScanLine свойство для обработки 24-битных растровых пикселей? Почему я предпочитаю использовать его, а не довольно часто используемое свойство Pixels?

  • 0
    Этот вопрос не является хорошим примером для этого сайта, и я голосую за то, чтобы закрыть его как неконструктивный. Сделайте то же самое, но не удаляйте его, пожалуйста. Я собираюсь расширить ответ (и исправить вашими предложениями) и, возможно, также изменить вопрос, чтобы лучше соответствовать здесь, но это займет некоторое время.
  • 9
    Этот вопрос в порядке. Не надо его закрывать. У кого-то может быть лучший ответ! И всегда есть сообщество вики.
Теги:
image-processing

1 ответ

61

1. Введение

В этом посте я попытаюсь объяснить использование свойства ScanLine только для формата 24-битного растрового пикселя, и если вам действительно нужно использовать его. Как сначала взгляните, что делает это свойство настолько важным.

2. ScanLine или нет...?

Вы можете спросить себя, почему использовать такую ​​сложную технику, как использование ScanLine свойство, похоже, когда вы можете просто использовать Pixels для доступа к вашим растровым пикселям. Ответ - большая разница в производительности, заметная при выполнении модификаций пикселей даже на относительно небольшой площади пикселей.

Свойство Pixels внутренне использует функции Windows API - GetPixel и SetPixel, для получения и установки значений цвета контекста устройства. Недостаток производительности в Pixels заключается в том, что вам обычно нужно получать значения цвета пикселя, прежде чем изменять их, что внутренне означает вызов обоих упомянутых Функции Windows API. Свойство ScanLine выигрывает эту гонку, потому что обеспечивает прямой доступ к памяти, в которой хранятся данные растрового пикселя. Прямой доступ к памяти происходит быстрее, чем два вызова функций Windows API.

Но это не значит, что свойство Pixels абсолютно плохое, и вам следует избегать его использования во всех случаях. Когда вы собираетесь изменять только несколько пикселей (не больших областей), например, тогда Pixels может быть достаточно для вас. Но не используйте его, когда вы собираетесь манипулировать областью пикселей.

3. Глубоко внутри пикселей

3.1 Исходные данные

Пиксельные данные растрового изображения (назовем их необработанными данными на данный момент), вы можете представить себе как одномерный массив байтов, содержащий последовательность значений интенсивности цветовых компонентов для каждого пикселя. Каждый пиксель в битовой карте состоит из фиксированного количества байтов в зависимости от используемого формата пикселей.

Например, формат 24-битного пикселя имеет 1 байт для каждого из его цветовых компонентов - для красного, зеленого и синего каналов. На следующем рисунке показано, как представить исходный массив байтов данных для такого 24-битного растрового изображения. Каждый цветной прямоугольник здесь представляет собой один байт:

Изображение 7775

3.2 Пример из практики

Представьте, что у вас есть 24-битное растровое изображение размером 3x2 (ширина 3px, высота 2px) и держите его в голове, потому что я попытаюсь объяснить некоторые внутренние элементы и показать принцип ScanLine. Он настолько мал только из-за пространства, необходимого для глубокого просмотра внутри (для тех, кто имеет яркое зрение, это зеленый пример такого изображения в формате png здесь Изображение 7776 : -)

3,3 Пиксельная композиция

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

Изображение 7777

В другом виде показано следующее изображение. Каждый ящик представляет собой один пиксель нашего воображаемого растрового изображения. В каждом пикселе вы можете видеть его координаты и группу из 3 байтов с их индексами из массива байтов необработанных данных:

Изображение 7778

4. Жизнь с цветами

4,1. Начальные значения

Как мы уже знаем, пиксели в нашем воображаемом 24-битном растровом файле состоят из 3 байт - 1 байт для каждого цветового канала. Когда вы создали это растровое изображение в своем воображении, все эти байты во всех пикселях были против вашей воли, инициализированной значением максимального байта - до 255. Это означает, что все каналы имеют максимальную интенсивность цвета:

Изображение 7779

Когда мы посмотрим, какой цвет будет смешаться с этими начальными значениями канала для каждого пикселя, мы увидим, что наше растровое изображение entirely white. Итак, когда вы создаете 24-битную растровую карту в Delphi, она изначально белая. Ну, по умолчанию белые будут растровыми в каждом пиксельном формате, но они могут отличаться от начальных значений байтов исходных данных.

5. Тайная жизнь ScanLine

Из приведенного выше чтения я надеюсь, что вы поняли, как данные растрового изображения хранятся в массиве байтов необработанных данных и как формируются отдельные пиксели из этих данных. Теперь перейдем к собственному свойству ScanLine и как он может быть полезен при прямой обработке необработанных данных.

5,1. Назначение ScanLine

Основное блюдо этого сообщения, свойство ScanLine, является индексированным свойством только для чтения, которое возвращает указатель на первый байт массив необработанных байтов данных, который принадлежит определенной строке в растровом изображении. Другими словами, мы запрашиваем доступ к массиву байтов необработанных данных для данной строки, и получаемый результат является указателем на первый байт этого массива. Индексный параметр этого свойства указывает индекс на основе 0 строки, для которой мы хотим получить эти данные.

Следующее изображение иллюстрирует наше воображаемое растровое изображение и указатели, которые мы получаем с помощью свойства ScanLine, используя различные индексы строк:

Изображение 7780

5.2. Преимущество ScanLine

Итак, из того, что мы знаем, мы можем заключить, что ScanLine дает нам указатель на некоторый массив байтов данных строки. И с этим массивом строк исходных данных мы можем работать - мы можем читать или перезаписывать его байты, но только в диапазоне границ массива определенной строки:

Изображение 7781

Ну, у нас есть массив интенсивностей цвета для каждого пикселя определенной строки. Рассмотрение итерации такого массива; было бы не очень удобно перебирать этот массив по одному байту и настраивать только одну из трех цветных частей пикселя. Лучше будет проходить через пиксели и каждый три цветовых байта корректировать с каждой итерацией - как и с Pixels, как мы это делали.

5.3. Прыжки через пиксели

Чтобы упростить цикл массива строк, нам нужна структура, соответствующая нашим данным пикселя. К счастью, для 24-битных растровых изображений существует структура RGBTRIPLE; в Delphi, переведенный как TRGBTriple. Эта структура, короче, выглядит так (каждый элемент представляет интенсивность одного цветового канала):

type
  TRGBTriple = packed record
    rgbtBlue: Byte;
    rgbtGreen: Byte;
    rgbtRed: Byte;
  end;

Поскольку я старался быть терпимым к тем, у кого есть версия Delphi ниже 2009 года, и потому что это делает код более понятным, я не буду использовать арифметику указателей для итерации, но массив с фиксированной длиной с указателем на него в следующем Примеры (арифметика указателя была бы менее читаема в Delphi 2009 ниже).

Итак, у нас есть структура TRGBTriple для пикселя, и теперь мы определяем тип массива строк. Это упростит итерацию пикселей растровых строк. Этот я только что заимствовал из подразделения ShadowWnd.pas(в любом случае, дома одного интересного класса). Вот он:

type
  PRGBTripleArray = ^TRGBTripleArray;
  TRGBTripleArray = array[0..4095] of TRGBTriple;

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

6. ScanLine на практике

6.1. Сделайте второй ряд черным

Начнем с первого примера. В этом случае мы объективизируем наше воображаемое растровое изображение, задаем его правильную ширину, высоту и формат пикселей (или, если хотите, бит глубины). Затем мы используем ScanLine с параметром строки 1, чтобы получить указатель на массив байтов исходных данных второй строки. Указатель, который мы получаем, присваиваем переменной RowPixels, которая указывает на массив TRGBTriple, поэтому с этого времени мы можем взять его как массив пикселов строк. Затем мы перебираем этот массив по всей ширине растрового изображения и устанавливаем все значения цвета каждого пикселя равным 0, что приводит к растровому изображению с первой белой строкой (по умолчанию используется белый цвет, как указано выше), и что делает вторую строку черной, Это растровое изображение затем сохраняется в файле, но не удивляйтесь, когда вы его видите, это действительно очень мало:

type
  PRGBTripleArray = ^TRGBTripleArray;
  TRGBTripleArray = array[0..4095] of TRGBTriple;

procedure TForm1.Button1Click(Sender: TObject);
var
  I: Integer;
  Bitmap: TBitmap;
  Pixels: PRGBTripleArray;
begin
  Bitmap := TBitmap.Create;
  try
    Bitmap.Width := 3;
    Bitmap.Height := 2;
    Bitmap.PixelFormat := pf24bit;
    // get pointer to the second row raw data
    Pixels := Bitmap.ScanLine[1];
    // iterate our row pixel data array in a whole width
    for I := 0 to Bitmap.Width - 1 do
    begin
      Pixels[I].rgbtBlue := 0;
      Pixels[I].rgbtGreen := 0;
      Pixels[I].rgbtRed := 0;
    end;
    Bitmap.SaveToFile('c:\Image.bmp');
  finally
    Bitmap.Free;
  end;
end;

6.2. Графическое изображение в оттенках серого с использованием яркости

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

Luminance = 0.299 R + 0.587 G + 0.114 B

Это значение яркости затем присваивается каждой цветовой составляющей итерированного пикселя:

type
  PRGBTripleArray = ^TRGBTripleArray;
  TRGBTripleArray = array[0..4095] of TRGBTriple;

procedure GrayscaleBitmap(ABitmap: TBitmap);
var
  X: Integer;
  Y: Integer;
  Gray: Byte;
  Pixels: PRGBTripleArray;
begin
  // iterate bitmap from top to bottom to get access to each row raw data
  for Y := 0 to ABitmap.Height - 1 do
  begin
    // get pointer to the currently iterated row raw data
    Pixels := ABitmap.ScanLine[Y];
    // iterate the row pixels from left to right in the whole bitmap width
    for X := 0 to ABitmap.Width - 1 do
    begin
      // calculate luminance for the current pixel by the mentioned formula
      Gray := Round((0.299 * Pixels[X].rgbtRed) +
        (0.587 * Pixels[X].rgbtGreen) + (0.114 * Pixels[X].rgbtBlue));
      // and assign the luminance to each color component of the current pixel
      Pixels[X].rgbtRed := Gray;
      Pixels[X].rgbtGreen := Gray;
      Pixels[X].rgbtBlue := Gray;
    end;
  end;
end;

И возможное использование вышеуказанной процедуры. Обратите внимание, что эту процедуру можно использовать только для 24-битных растровых изображений:

procedure TForm1.Button1Click(Sender: TObject);
var
  Bitmap: TBitmap;
begin
  Bitmap := TBitmap.Create;
  try
    Bitmap.LoadFromFile('c:\ColorImage.bmp');
    if Bitmap.PixelFormat <> pf24bit then
      raise Exception.Create('Incorrect bit depth, bitmap must be 24-bit!');
    GrayscaleBitmap(Bitmap);
    Bitmap.SaveToFile('c:\GrayscaleImage.bmp');
  finally
    Bitmap.Free;
  end;
end;

7. Связанные чтения

  • 2
    Если у вас есть предложения или что-то исправить, не стесняйтесь комментировать или просто изменить сообщение. Это только начальная версия, и я надеюсь, что она будет расти. По крайней мере, я хочу немного изменить иллюстрации (поскольку они мои :-) и сосредоточиться на большем количестве и лучших примерах.
  • 2
    Поскольку вы показали макет памяти, вам может потребоваться расширить вопрос о том, почему мы хотим работать «только в пределах границ массива определенной строки» , то есть фактического макета памяти. Fi для образца 3x2, почему Bitmap.Scanline[1] - Bitmap.Scanline[0] - это не «9», а «12» (или фактически «-12» в большинстве случаев). (не мой голос ..)
Показать ещё 7 комментариев

Ещё вопросы

Сообщество Overcoder
Наверх
Меню