Определение размера изображения JPEG (JFIF) - PullRequest
30 голосов
/ 13 октября 2009

Мне нужно найти размер изображения JPEG (JFIF). Изображение не сохраняется как отдельный файл, поэтому я не могу использовать GetFileSize или какой-либо другой API, например этот (изображение помещается в поток, и никакой другой заголовок отсутствует, кроме обычного заголовка JPEG / JFIF (ы)).

Я провел некоторое исследование и обнаружил, что изображения JPEG состоят из разных частей, каждая часть начинается с маркера кадра (0xFF 0xXX) и размера этого кадра. Используя эту информацию, я смог разобрать много информации из файла.

Проблема в том, что я не могу найти размер сжатых данных, так как кажется, что для сжатых данных нет маркера кадра. Кроме того, кажется, что сжатые данные следуют за маркером SOS (FFDA), а изображение заканчивается маркером Конец изображения (EOI) (FFD9).

Одним из способов достижения этой цели является поиск маркера EOI от байта к байту, но я думаю, что сжатые данные могут содержать эту комбинацию байтов, верно?

Существует ли простой и правильный способ определения общего размера изображения? (Я бы предпочел некоторый код / ​​идею без какой-либо внешней библиотеки )

В основном мне нужно расстояние (в байтах) между началом изображения (SOI- FFE0) и концом изображения (EOI- FFD9).

Ответы [ 4 ]

38 голосов
/ 21 октября 2009

Сжатые данные не будут включать байты SOI или EOI, поэтому вы в безопасности. Но комментарий, данные приложения или другие заголовки могут. К счастью, вы можете определить и пропустить эти разделы по мере того, как указана длина.

Спецификация JPEG говорит вам, что вам нужно:
http://www.w3.org/Graphics/JPEG/itu-t81.pdf

Посмотрите на Таблицу B.1, на странице 32. Символы, которые имеют *, не имеют поля длины после него (RST, SOI, EOI, TEM). Другие делают.

Вам нужно будет пропустить различные поля, но это не так уж плохо.

Как пройти:

  1. Начать чтение SOI (FFD8). Это начало. Это должно быть первым делом в потоке.

    • Затем просмотрите файл, найдите больше маркеров и пропустите заголовки:

    • Маркер SOI (FFD8): поврежденное изображение. Вы должны были уже найти ВЗ!

    • TEM (FF01): автономный маркер, продолжайте движение.

    • RST (FFD0 - FFD7): автономный маркер, продолжайте движение. Вы можете проверить, что маркеры перезапуска отсчитывают от FFD0 до FFD7 и повторяются, но это не обязательно для измерения длины.

    • Маркер EOI (FFD9): Готово!

    • Любой маркер, который не является RST, SOI, EOI, TEM (от FF01 до FFFE, за исключением исключений выше): после маркера прочитайте следующие 2 байта, это большой 16-бит - конечная длина заголовка этого кадра (не включая 2-байтовый маркер, но включая поле длины). Пропустите указанное количество (обычно длина минус 2, поскольку вы уже получили эти байты).

    • Если перед EOI вы получили конец файла, значит, у вас поврежденное изображение.

    • Как только вы получили EOI, вы прошли через JPEG и должны иметь длину. Вы можете начать снова с чтения другого SOI, если вы ожидаете, что в вашем потоке будет более одного JPEG.

2 голосов
/ 05 января 2010

Может быть, что-то вроде этого

int GetJpgSize(unsigned char *pData, DWORD FileSizeLow, unsigned short *pWidth, unsigned short *pHeight)
{
  unsigned int i = 0;


  if ((pData[i] == 0xFF) && (pData[i + 1] == 0xD8) && (pData[i + 2] == 0xFF) && (pData[i + 3] == 0xE0)) {
    i += 4;

    // Check for valid JPEG header (null terminated JFIF)
    if ((pData[i + 2] == 'J') && (pData[i + 3] == 'F') && (pData[i + 4] == 'I') && (pData[i + 5] == 'F')
        && (pData[i + 6] == 0x00)) {

      //Retrieve the block length of the first block since the first block will not contain the size of file
      unsigned short block_length = pData[i] * 256 + pData[i + 1];

      while (i < FileSizeLow) {
        //Increase the file index to get to the next block
        i += block_length; 

        if (i >= FileSizeLow) {
          //Check to protect against segmentation faults
          return -1;
        }

        if (pData[i] != 0xFF) {
          return -2;
        } 

        if (pData[i + 1] == 0xC0) {
          //0xFFC0 is the "Start of frame" marker which contains the file size
          //The structure of the 0xFFC0 block is quite simple [0xFFC0][ushort length][uchar precision][ushort x][ushort y]
          *pHeight = pData[i + 5] * 256 + pData[i + 6];
          *pWidth = pData[i + 7] * 256 + pData[i + 8];

          return 0;
        }
        else {
          i += 2; //Skip the block marker

          //Go to the next block
          block_length = pData[i] * 256 + pData[i + 1];
        }
      }

      //If this point is reached then no size was found
      return -3;
    }
    else {
      return -4;
    } //Not a valid JFIF string
  }
  else {
    return -5;
  } //Not a valid SOI header

  return -6;
}  // GetJpgSize
2 голосов
/ 13 октября 2009

Поскольку у вас нет опубликованного языка, я не уверен, что это будет работать, но:

Можете ли вы Stream.Seek(0, StreamOffset.End);, а затем занять позицию потока?

Пожалуйста, уточните, какую платформу вы используете.

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

EDIT

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

OGG отлично подойдет для этого.

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

0 голосов
/ 13 октября 2009

В python вы можете просто прочитать весь файл в строковый объект и найти первое вхождение FF E0 и последнее вхождение FF D9. Предположительно, это начало и конец, который вы ищете?

f = open("filename.jpg", "r")
s = f.read()
start = s.find("\xff\xe0")
end = s.rfind("\xff\xd9")
imagesize = end - start
...