Войти
ПрограммированиеСтатьиГрафика

Описание формата TGA.

Автор:

Здесь будет описан 24-х и 32-х битный формат файла TGA с компрессией RLE и без неё.

НАЧАЛО:

ЗАГОЛОВОК:

КОЛ-ВО БАЙТ  ТИП ПЕРЕМЕННОЙ    НАЗВАНИЕ  ОПИСАНИЕ
1    char      IdLeight  Длина текстовой информации после первого
18-ти байтового блока. Может
быть использована для описания файла
1    char      ColorMap  Идентификатор наличия цветовой карты, здесь не описан - устарел
1    char      DataType  Тип данных - запакованный или нет
5    char[5]      ColorMapInfo  Информация о цветовой карте - нужно пропустить эти 5 байт
2    int      X_Origin  Начало изображения по оси X
2    int      Y_Origin  Начало изображения по оси Y
2    int      Width    Ширина изображения
2    int      Height    Высота изображения
1    char      BitPerPel  Кол-во бит на пиксель - здесь только 24 или 32
char      Description  Описание - пропускайте
size = IdLeight  unknown      unknown    Пропустите IdLeight байт и все (IdLeight - самое первое поле)

ДАННЫЕ:

1. Неупакованные данные — DataType должен быть равен 2

При неупакованных данных информация о пикселях будет содержаться поочередно. Это значит вам нужно будет прочитать столько информации сколько пикселей в изображении умноженное на кол-во бит на пиксель

ПРИМЕР:

unsigned char  R, G, B, A;
for(unsigned int i = 0; i < Width * Height; i++)
{
  fread(&R, 1, 1, fp);
  fread(&G, 1, 1, fp);
  fread(&B, 1, 1, fp);
  if(BitsPerPel == 32)
    fread(&A, 1, 1, fp);

  pImage[i * (BPP / 8) + 0] = B;
  pImage[i * (BPP / 8) + 1] = G;
  pImage[i * (BPP / 8) + 2] = R;
  if(BitsPerPel == 32)
    pImage[i * (BPP / 8) + 3] = A;
}

2.Упакованные данные — DataType должен быть равен 10

При таком расположении информации (данных), эта самая информация может быть либо запакована, либо нет. После чтения ЗАГОЛОВКА вам нужно прочитать ровно один байт, чтобы узнать как хранятся данные:

КОЛ-ВО БАЙТ  ТИП ПЕРЕМЕННОЙ    НАЗВАНИЕ  ОПИСАНИЕ
1    unsigned char    BlockInfo  Описание формата хранящихся данных

Заметьте, что здесь мы используем именно беззнаковую переменную - это важно, т.к. значение, хранящееся в BlockInfo может быть больше чем 128 - максимальное значение для переменной типа CHAR.

Для того, чтобы узнать что за информация лежит здесь, вам нужно проверить состояние первого бита в переменной BlockInfo

ПРИМЕР:

WhatItIs = BlockInfo & 128

Почему именно 128? Посмотрим на ее двоичный код

1000 0000

Как видите, в начале стоит единица, а при операции «И» любой ноль перекрывает единицу, а единица с единицей даст единицу,
так что если состояние BlockInfo было к примеру

1011 0111

То после операции И в переменно WhatItIs будет вот что

1000 0000

То есть, видно, что мы получим состояние первого бита

А если BlockInfo было равно

0111 0101

То после операции И в переменной WhatItIs было бы

0000 0000

То есть, в первом бите уже не 1 а 0 :))

Так вот, продолжим...

После того, как проверим что за состояние у нас имеет первый бит, мы сможем узнать информацию о том, какие данные нам даны:
Если состояние первого бита равно единице- то это запакованные данные
Если состояние первого бита равно нулю - то это незапакованные данные
ПРИМЕР:

if(BlockInfo & 128)
{
  //...
}
else
{
  //...
}

В первом блоке скобок запакованные данные, во втором блоке скобок незапакованные.

Пока мы разобрались лишь с первым битом, а в одной байте их 8, но т.к. один уже использован, то нам остаются только 7. Что же с ними делать? И для чего они нужны и как узнать их состояние всех сразу?

В оставшихся 7 битах хранится кол-во пикселей-1 которые идут после чтения нашей однобайтовой переменной. Далее все объясню.

Для того чтобы узнать какое число хранится в этих 7 битах мы проделаем все то, что делали в начале, только вместо

1000 0000

возьмём

0111 1111

В десятичной системе это число равно 127-ми. Таким образом, чтобы узнать количество пикселей, нужно сделать следующие действия:
NumPixels = BlockInfo & 127

Теперь мы знаем как хранятся наши данные и сколько их нужно обработать, посмотрим что получилось:

//----------------------
//После чтения заголовка
//----------------------
unsigned char  BlockInfo;
char    WhatItIs;
unsigned int  NumPixels;
for(unsigned int i = 0; i < Width * Height;)
{
  fread(&BlockInfo, 1 , 1, fp);

  WhatItIs = BlockInfo & 128;
  NumPixels = BlockInfo & 127;

  //--------------------------
  //Если запакованные данные
  //--------------------------
  if(WhatItIs)
  {
    ...
  }
  //-----
  //Иначе
  //-----
  else
  {
    ...
  }
}

Итак, начнем обсуждать ЗАПАКОВАННЫЕ данные и НЕ запакованные.

1. Запакованные

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

После того первого байта идет информация о единственно пикселе и тут нужно знать сколько бит идет на пиксель
Если у нас на пиксель идет 24 бита, то нам нужно прочитать 3 байта(24 / 8 = 3), а если 32 бита то 4 байта
После того, как мы прочитаем эти 24 или 32 бита нужно просто-напросто вложить в наш массив пикселей столько одинаковых пикселей, сколько их записано в переменной NumPixels

ПРИМЕР:

//----------------------------
//Выделение памяти под пиксели
//----------------------------
unsigned char  *Pixels = new unsigned char[Width * Height * (BitsPerlPel / 8)];

unsigned char  BlockInfo, R, G, B, A;
char    WhatItIs;
unsigned int  NumPixels;
for(unsigned int i = 0; i < Width * Height;)
{
  fread(&BlockInfo, 1 , 1, fp);

  WhatItIs = BlockInfo & 128;
  NumPixels = BlockInfo & 127;

  //--------------------------
  //Если запакованные данные
  //--------------------------
  if(WhatItIs)
  {
    fread(&R, 1, 1, fp);
    fread(&G, 1, 1, fp);
    fread(&B, 1, 1, fp);
    if(BitsPerPel == 32)
      fread(&A, 1, 1, fp);

    for(unsigned int k = 0; k < NumPixels + 1; k++)
    {
      Pixels[i * (BitsPerPel / 8) + 0] = B;
      Pixels[i * (BitsPerPel / 8) + 1] = G;
      Pixels[i * (BitsPerPel / 8) + 2] = R;
      if(BitsPerPel == 32)
        Pixels[i * (BitsPerPel / 8) + 3] = A;
      //-----------------------------------
      //Увеличиваем счетчик кол-ва пикселей
      //-----------------------------------
      n++;
    }
  }
  //-----
  //Иначе
  //-----
  else
  {
    ...
  }
}

Вы можете спросить, почему мы прибавили единицу к переменной - это сделано потому, что при изымании кол-ва пикселей, значение которое мы получим всегда меньше на единицу, это сделано из-за того, что у нас осталось только 7 бит, было бы 8 мы бы ничего не прибавляли.

2. Незапакованные данные

При незапакованных данных информация хранится о каждом пикселе последовательно, то есть, если в переменной NumPixels было бы значение 20, то нам нужно было бы прочитать 20 пикселей. То есть, просто читаете информацию о пикселях и все.

ПРИМЕР:

//----------------------------
//Выделение памяти под пиксели
//----------------------------
unsigned char  *Pixels = new unsigned char[Width * Height * (BitsPerlPel / 8)];

unsigned char  BlockInfo, R, G, B, A;
char    WhatItIs;
unsigned int  NumPixels;
for(unsigned int i = 0; i < Width * Height;)
{
  fread(&BlockInfo, 1 , 1, fp);

  WhatItIs = BlockInfo & 128;
  NumPixels = BlockInfo & 127;

  //--------------------------
  //Если запакованные данные
  //--------------------------
  if(WhatItIs)
  {
    ...
  }
  //-----
  //Иначе
  //-----
  else
  {
    for(unsigned int k = 0; k < NumPixels + 1; k++)
    {
      fread(&R, 1, 1, fp);
      fread(&G, 1, 1, fp);
      fread(&B, 1, 1, fp);
      if(BitsPerPel == 32)
        fread(&A, 1, 1, fp);
  
      Pixels[i * (BitsPerPel / 8) + 0] = B;
      Pixels[i * (BitsPerPel / 8) + 1] = G;
      Pixels[i * (BitsPerPel / 8) + 2] = R;
      if(BitsPerPel == 32)
        Pixels[i * (BitsPerPel / 8) + 3] = A;
      //-----------------------------------
      //Увеличиваем счетчик кол-ва пикселей
      //-----------------------------------
      n++;
  }
}

Как видите все очень легко и просто.

ВОТ ПОЛНЫЙ ПРИМЕР:

unsigned char  BlockInfo, R, G, B, A;
char    WhatItIs;
unsigned int  NumPixels;
for(unsigned int i = 0; i < Width * Height;)
{
  fread(&BlockInfo, 1 , 1, fp);

  WhatItIs = BlockInfo & 128;
  NumPixels = BlockInfo & 127;

  //--------------------------
  //Если запакованные данные
  //--------------------------
  if(WhatItIs)
  {
    fread(&R, 1, 1, fp);
    fread(&G, 1, 1, fp);
    fread(&B, 1, 1, fp);
    if(BitsPerPel == 32)
      fread(&A, 1, 1, fp);

    for(unsigned int k = 0; k < NumPixels + 1; k++)
    {
      Pixels[i * (BitsPerPel / 8) + 0] = B;
      Pixels[i * (BitsPerPel / 8) + 1] = G;
      Pixels[i * (BitsPerPel / 8) + 2] = R;
      if(BitsPerPel == 32)
        Pixels[i * (BitsPerPel / 8) + 3] = A;
      //-----------------------------------
      //Увеличиваем счетчик кол-ва пикселей
      //-----------------------------------
      n++;
    }
  }
  //-----
  //Иначе
  //-----
  else
  {
    for(unsigned int k = 0; k < NumPixels + 1; k++)
    {
      fread(&R, 1, 1, fp);
      fread(&G, 1, 1, fp);
      fread(&B, 1, 1, fp);
      if(BitsPerPel == 32)
        fread(&A, 1, 1, fp);
  
      Pixels[i * (BitsPerPel / 8) + 0] = B;
      Pixels[i * (BitsPerPel / 8) + 1] = G;
      Pixels[i * (BitsPerPel / 8) + 2] = R;
      if(BitsPerPel == 32)
        Pixels[i * (BitsPerPel / 8) + 3] = A;
      //-----------------------------------
      //Увеличиваем счетчик кол-ва пикселей
      //-----------------------------------
      n++;
  }
}

#TGA

27 октября 2003 (Обновление: 17 сен. 2009)

Комментарии [32]