Растровая Графика


Необходимость Растровой Графики


До 1980-го года, под Компьютерной Графикой понималось исключительно Векторная Графика. Векторные дисплеи удивляли быстрой прорисовкой линейных графиков, которые были намного элегантней толстых горизонтальных строк телевизоров. В то время считалось абсурдным, что когда-нибудь телевизоры заменят элегантные компьютерные (векторные) изображения. Никто не считал недостатком то, что векторные мониторы не могут залить какие-то области, и что эти мониторы не имеют возможности рисовать разными цветами.

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

Так был создан один из базовых элементов компьютера, который сегодня мы называем графическая карта, включающая в себя:
1) быстрая RAM для записи растровой матрицы (= видео-память)
2) быстрая адресация матрицы синхронно с аналоговыми сигналами вертикальной и горизонтальной развертки (=видео-контроллер).
3) быстрый цифровой-аналоговый преобразователь, который преобразовывает данные матрицы в аналоговый видео сигнал ( =ЦАП)

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

Заливка площадей, многоцветность, повышение разрешения и частоты развертки обеспечили прорыв растровой графике, не смотря на то, что она все еще не позволяла отображать тонкие линии и кривые (это было возможно только через лестничную симуляцию). Растровая графика тянет за собой огромную редундантность и тяжела в программировании. По этой причине почти за каждой растровой графикой стоит векторная графика (исключения - фото и видео).
Растровая Матрица
Растровая матрица – это прямоугольная организация данных (типа Byte, UInt16, UInt32 или Color) по ширине в виде колонок и по высоте в виде строк.
Индекс колонок - column index: x, где 0 <= x < width.
Индекс строк - row index: y, где 0 <= y < height.
Пример:
Белый человечек (Homunculus) на черном фоне
Количество колонок = width = 9
Количество строк = height = 10

0 0 0 9 9 9 0 0 0
0 0 0 9 3 9 0 0 0 [4,1] = Рот
0 0 0 9 9 9 0 0 0
0 0 0 0 9 0 0 0 0 [4,3] = Шея
3 4 5 9 9 9 5 4 3 [0,4] и [8,4] = Руки
0 0 0 9 8 9 0 0 0 [4,5] = Пупок
0 0 0 9 9 9 0 0 0
0 0 0 6 0 6 0 0 0
0 0 0 5 0 5 0 0 0
0 0 0 5 0 5 0 0 0 [3,9] und [5,9] = Ноги


Примеры определения матрицы изображения M с 32-Bit-цветом пикселя = ARGB-Пиксел:
C++ как Array : int M[height][width];
Java как Array : int[][] M = new int[height][width];
C# как Array : Color[,] M = new Color[height, width];
C# как Bitmap: Bitmap M = new Bitmap( width, height, PixelFormat.Format32bppArgb );
На первый взгляд странно, но во всех языковых вариантах, при определении Растровых-Массивов, требуется указание y-координат до х-координат. Причина – Матрицы разворачиваются в памяти компьютера, как одномерные линейные массивы. Интуитивно понятная последовательность - 0-я строка, 1-я строка и т.д. до (height-1)-й строки – возможна только если первый индекс – y.
Следствие: если мы хотим сделать рот Человечка (Homunculus) черным, то должны написать следующий код:
C++ как Array : Homunculus[1][4] = 0;
Java как Array : Homunculus[1][4] = 0;
C# как Array : Homunculus[1,4] = Color.Black;
C# как Bitmap: Homunculus.SetPixel( 4, 1, Color.Black );

Линейная адресация


Растровая Матрица – это лишь словесная конструкция, так как компьютер понимает лишь линейное адресное пространство и записывает все матрицы линейно. В качестве аналогии [y][x]-Матрицу лучше всего представить в виде комода с выдвижными ящиками. В памяти компьютера все ящики вынуты и разложены друг за другом. Сначала идет Строка 0, затем Строка 1 и т.д. до строки height-1. При обращении к пикселу M[y][x], на самом деле высчитывается линейный адрес M + y * width + x. Это значит, что за удобный доступ в виде M[y][x] приходится платить двумя дополнительными сложениями и одним умножением.


Следовательно: при миллионах пикселей матричная адресация очень медлена.
Лучше: использование указателей для быстрых операций на изображениях.

Пример: медленный код обнуления матрицы M[height][width]:
for ( y=0; y < height; y++ )
   for ( x=0; x < width; x++ )
   M[y][x] = 0;

Пример: быстрый код для обнуления матрицы M[height][width]: 
int* pointer = M; 
for ( i=0; i < width*height; i++ ) *pointer++ = 0;

Pixel

Pixel - это сокращение от "Picture Element" обозначающее минимальный не квантуемый элемент Растровой Матрицы. Матрица всегда содержит Пикселы только одного типа, при этом существует множество типов Пикселей или их форматов.
Примеры:

1bppIndexed  1 bit per pixel with indexed color. Requires a LUТ with 2 colors in it. For binary images.
4bppIndexed  4 bits per pixel, indexed. Requires a LUT with 3x16 palette entries.
8bppIndexed  8 bits per pixel, indexed. Requires a LUT with 3x256 palette entries.
16bppGrayScale  16 bits per pixel. The color information specifies 65536 shades of gray.
16bppRgb55516 bits per pixel; 5 bits each are used for the red, green, and blue components. The remaining bit is not used.
24bppRgb24 bits per pixel; 8 bits each are used for the red, green, and blue components.
32bppArgb32 bits per pixel; 8 bits each are used for the alpha, red, green, and blue components.
32bppRgb32 bits per pixel; 8 bits each are used for the red, green, and blue components. The remaining 8 bits are not used.
64bppArgb64 bits per pixel; 16 bits each are used for the alpha, red, green, and blue components.

False Color Picture = определение для первых 3-х форматов 1bppIndexed, 4bppIndexed und 8bppIndexed.
Gray Scale Picture = 16bppGrayScale.
True Color Picture = определение для всех других форматов.

Наиболее часто используемые форматы:

1bppIndexed → для бинарных картинок и черно-белых принтеров - для максимальной экономии объема данных.

32bppRgb → для цветных фотографий так как 32-х битная архитектура и 32-х битная адресация отлично подходит для компьютеров.

Битовая раскладка одного пиксела:

32bppRgb,

24bppRgb und

16bppRgb555

Сравнение Векторная Графика ↔ Растровая Графика

Общее сравнение

Векторная ГрафикаРастровая Графика
Основная структура данныхПолигон:
PointF[] p = new PointF[n];
Матрица:
Bitmap bmp = new Bitmap( width, height, PixelFormat.Format32bppArgb );
Прочие СтруктурыПрямоугольник, Эллипс, СплайнRLC, Crack Code, MPEG
Форматы файловWMF, PostScript, XAML, PDF, FlashBMP, GIF, JPEG, MPEG, PNG, TIFF, AVI
Объем памятинизкий: n*sizeof(PointF)огромный: width*height*sizeof(Color)
Тонкие линииотличноТолько вертикальные и горизонтальные линии
Заливка площадейтолько штриховкойхорошо, но с грубыми краями
Шрифтыслишком тонкие, но хорошо масштабируемые = один шрифт для всех размеров  = TrueTypeхорошо, но с грубыми краями, для каждого размера нужен свой шрифт.
Реальные картинкиноль, только контурыхорошо, TV
Многоцветностьпочти всегда одноцветная, максимум 2 цвета возможны.хорошо: почти всегда RGB
Источник созданияпочти всегда - Человекпочти всегда Машина:
a) цифровые фото реального мира (Фото-Видео-Камера)
b) рендер из Векторной Графики (Графическая карта)
МатематикаРаботают все законы аналитической геометрии.работает только новая Цифровая Геометрия
Мерцание мерцает только если у Полигона много угловмерцает независимо от содержания картинки
ПредназначениеКонтурное Рисование : CAD, КомиксыКартинки с цветными или серыми площадями
flüchtige AusgabeCRT = Векторные мониторыСтрочные-CRT, Flat Panel Display = Растровые мониторы
dauerhafte AusgabeПлоттерПринтер

Сравнение операций scroll, zoom, rotate

scroll, zoom, rotate Полигона p0 → p1scroll, zoom, rotate Битмапа bmp0 → bmp1
все x,y - действительные числавсе x,y - целые числа
гладкостьступенчатость, пикселизация
всегда высокая точностьпочти всегда ошибка округления
прямая трансформация из p0 в p1:
трансформируем каждый Вертекс из p0 в p1
Обратная трансформация из bmp1 в bmp0:
ищем для каждого Пиксела из bmp1 один Пиксел из bmp0
нет границбольшая проблема: потери на границах картинки
полная реверсивностьОперации почти никогда нельзя сделать обратными
операции можно делать последовательноОперации всегда должны проводится с оригиналом bmp0!
p0 можно перезаписать значениями p1 bmp0 всегда нужен в оригинале, нельзя перезаписывать значениями из bmp1!

Сравнение кода операции Scroll (Сдвиг на: float dx, float dy)

Vektor-Scroll Полигона p0 → p1Raster-Scroll Битмапа bmp0 → bmp1
for all 0 <= i < n { 
  p1[i].x = p0[i].x + dx; 
  p1[i].y = p0[i].y + dy; 
}
int idx = Convert.ToInt32( dx ); //rounding 
int idy = Convert.ToInt32( dy ); //rounding 
for ( int y1=0; y1 < bmp1.Height; y1++ ) { 
   int y0 = y1 - idy; //backward 
   if ( y0 < 0 || y0 >= bmp0.Height ) 
   continue; //outside 
   for ( int x1=0; x1 < bmp1.Width; x1++ ) { 
      int x0 = x1 - idx; //backward 
      if ( x0 < 0 || x0 >= bmp0.Width ) continue; //outside 
      Color color = bmp0.GetPixel( x0, y0 ); 
      bmp1.SetPixel( x1, y1, color ); 
   } 
}

Сравнение кода операции Zoom (Масштабирование: float zoomx, float zoomy)

Vektor-Zoom Полигона p0 → p1Raster-Zoom Битмапа bmp0 → bmp1
//Центр растяжения в точке(0/0) 
for all 0 <= i < n { 
   p1[i].x = p0[i].x * zoomx; 
   p1[i].y = p0[i].y * zoomy; 
}
for ( int y1=0; y1 < bmp1.Height; y1++ ) { 
   int y0 = Convert.ToInt32( y1 / zoomy ); //backward 
   if ( y0 < 0 || y0 >= bmp0.Height ) continue; //outside 
   for ( int x1=0; x1 < bmp1.Width; x1++ ) { 
      int x0 = Convert.ToInt32( x1 / zoomx ); //backward 
      if ( x0 < 0 || x0 >= bmp0.Width ) continue; //outside 
      Color color = bmp0.GetPixel( x0, y0 ); 
      bmp1.SetPixel( x1, y1, color ); 
   } 
}

Сравнение кода операции Rotation (Поворот на α Grad по часовой стрелке)

Vektor-Rotation на угол α Полигона p0 → p1Raster-Rotation на угол α Битмапа bmp0 → bmp1
//Центр вращения в точке (0/0) 
double arcus = alpha * 2 * Math.PI / 360; 
float sinus = (float)Math.Sin( arcus ); 
float cosinus = (float)Math.Cos( arcus ); 
for all Angles 0 <= i < n { 
   p1[i].x = p0[i].x * cosinus - 
              p0[i].y * sinus; 
   p1[i].y = p0[i].x * sinus + 
              p0[i].y * cosinus; 
}
double arcus = alpha * 2 * Math.PI / 360; 
float sinus = (float)Math.Sin( arcus ); 
float cosinus = (float)Math.Cos( arcus ); 
for ( int y1=0; y1 < bmp1.Height; y1++ ) { 
  float y1_sinus = y1 * sinus; 
  float y1_cosinus = y1 * cosinus; 
   for ( int x1=0; x1 < bmp1.Width; x1++ ) { 
      int x0 = Convert.ToInt32( x1 * cosinus + y1_sinus ); 
      if ( x0 < 0 || x0 >= bmp0.Width ) continue; 
      int y0 = Convert.ToInt32( -x1 * sinus + y1_cosinus ); 
      if ( y0 < 0 || y0 >= bmp0.Height ) continue; 
      Color color = bmp0.GetPixel( x0, y0 ); 
      bmp1.SetPixel( x1, y1, color ); 
   } 
}

1) Оригинал 11x11 raster 2) Ожидаемый результат поворота
 
3) Приближение пиксела, который накрывает 4-е оригинальных пиксела 4) Реальный результат после билинейной фильтрации


copied from M. Gianaris
 
 

Внимание: Результат выше приведенной операции вращения плохой, т.к. округления
int x0 = Convert.ToInt32( x1 * cosinus + y1_sinus ) 
и
int y0 = Convert.ToInt32( -x1 * sinus + y1_cosinus )
создают тяжелые артефакты.

Улучшение: Можно дополнить операцию вращения Билинейным фильтром, который собирает целевой пиксел из 4-х оригинальных, накрытых целевым, с учетом процентного отношения соответствующих площадей. Но и этот способ не лишен артефактов, как видно на картинке 4).

Дополнительные замечания к операции Raster-Rotation:
DotNet содержит в классе Graphics один элегантный вариант метода DrawImage(...).
Этот вариант принемает параметры  p[0], p[1] и p[2] которые образуют параллелограмм. DrawImage(...) выполняет вращение и сдвиг растровой картинки так, что углы картинки ложатся точно в вершины параллелограмма.
Это значит, что нам достаточно развернуть треугольник  PointF[] p = new PointF[3]; через операцию векторного вращения, передать повернутый треугольник  
в DrawImage(myBitmap, p) полное растровое вращение, включая билинейную фильтрацию будет выполнено автоматически.
Смотрите: http://msdn.microsoft.com/en-us/library/system.drawing.graphics.drawimage.aspx