Войти
ПрограммированиеСтатьиОбщее

Работа с файлами форматов ZIP, JPEG и PNG. (2 стр)

Автор:

Чтение JPEG-формата

Как и в художественной литературе, самое страшное и интересное преподносится в конце повествования. Разработчики этой библиотеки решили идти не по пути удобства, а по пути полной универсальности. Для чего предоставили возможность не только изменить I/O поток, но еще и разрешают заместить менеджер памяти, обработку ошибок и многое другое. В связи с этим, использование библиотечки превращается в небольшой кошмар на яву. Но как говориться «глаза бояться, а руки делают», приступим.

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

// структура содержащая необходимую для этого информацию
struct cDataManagerJPEGError {
    jpeg_error_mgr  pub;      // ссылочка на стандартный менеджер
    jmp_buf    setjmp_buffer;  // jump-буфера, идентификатор прыжка
};

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

// наша функция обработки ошибок
void JPEGErrorExit(j_common_ptr cinfo)
{
    // получаем ссылочку на нашу структуру
    cDataManagerJPEGError* myerr = (cDataManagerJPEGError*)cinfo->err;
    // выводим сообщение об ошибке (наверное можно убрать)
    (*cinfo->err->output_message)(cinfo);
    // делаем прыжок на очистку данных и ретурн ошибки
    longjmp(myerr->setjmp_buffer, 1);
}

Ее задача вызов функции longjmp(), которая восстанавливает окружение и перескакивает в точку, непосредственно следующую за надлежащим вызовом setjmp(). Второй параметр — значение, возвращаемое функцией setjmp(). В случае успешного выполнения она возвращает 0.

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

struct cDataManagerJPEGSource {
    jpeg_source_mgr  pub;    // ссылочка на стандартный менеджер
    cFile      *file;  // открытый файл
    JOCTET    *buffer;  // буфер данных
    bool      sof;    // признак того, что файл только что открыли
};

Чтение информации в JPEG библиотеке происходит следующим образом: в буфер из файла заносится блок данных (работает функция JPEGFillInputBuffer()), который читается кусками по мере надобности. При чтении изменяются переменные у стандартного менеджера данных — next_input_byte и bytes_in_buffer - соответственно первая увеличивается, а вторая уменьшается. Как только вторая переменная достигает нуля, читается новый блок данных. Когда файл заканчивается, в буфер заносится специальный признак о конце файла.

  // вызывается, когда переменная bytes_in_buffer достигает 0 и возникает
  // необходимость в новой порции информации. возвращает TRUE, если буфер
  // перезаполнен успешно, и FALSE если произошла ошибка ввода/вывода.
  boolean JPEGFillInputBuffer(j_decompress_ptr cinfo)
  {
    cDataManagerJPEGSource* src = (cDataManagerJPEGSource*)cinfo->src;
    // читаем кусками по c_JPEGInputBufferSize байт
    size_t nbytes = src->file->Read(src->buffer, sizeof(JOCTET), c_JPEGInputBufferSize);
    // если мы ничего не считали :(
    if (nbytes <= 0) {
      if ( src->sof )  return(FALSE); // блин, нам дали пустой файл - заорем "нехорошо" :)
      // если уже читали до этого, то вставляем в буфер инфу о конце файла
      src->buffer[0] = (JOCTET) 0xFF;
      src->buffer[1] = (JOCTET) JPEG_EOI;
      nbytes = 2;
    }

    // загоняем инфу в буфер, и размер скока прочли
    src->pub.next_input_byte = src->buffer;
    src->pub.bytes_in_buffer = nbytes;
    src->sof = false;  // файл не пустой, пронесло :)))
    // возвращаем успешное выполнение операции
    return(TRUE);
  }

Вышеприведенная функция, является основной. В дополнение к ней, придется переопределить еще несколько: JPEGInitSource() – необходима для инициализации источника данных, она вызывается до первого обращения к чтению в буфер данных. В ней мы, просто, установим флаг sof в положение «файл только что открыт», он пригодится для проверки на пустой файл. JPEGSkipInputData() вызывается, когда надо пропустить несколько байт в файле (seek вперед). И JPEGTermSource() для очистки данных по завершении чтения информации.

Функция настройки библиотеки на наш менеджер данных:

  void JPEGStdioSrc(j_decompress_ptr cinfo, cFile* file)
  {
    cDataManagerJPEGSource* src = 0;
    // смотрим, выделена ли память под JPEG-декомпрессор менеджер?
    // возможна ситуация, когда происходит одновременное обращение к источнику
    // от нескольких библиотек
    if (cinfo->src == 0) {
      // выделим память под наш менеджер, и установим на него указатель глобальной структуры
      // библиотеки. так как я использую менеджер памяти библиотеки JPEG то позаботится об
      // освобождении она сама. JPOOL_PERMANENT - означает что эта память выделяется на все
      // время работы с библиотекой
      cinfo->src = (struct jpeg_source_mgr *) (*cinfo->mem->alloc_small) 
        ((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(cDataManagerJPEGSource));
      src = (cDataManagerJPEGSource*) cinfo->src;
      // выделяю память для буффера данных, прочитанных из файла
      src->buffer = (JOCTET*) (*cinfo->mem->alloc_small) 
        ((j_common_ptr) cinfo, JPOOL_PERMANENT, c_JPEGInputBufferSize * sizeof(JOCTET));
      memset(src->buffer, 0, c_JPEGInputBufferSize * sizeof(JOCTET));
    }
    // для краткости - сестры таланта
    src = (cDataManagerJPEGSource*)cinfo->src;
    // настраиваем обработчики событий на наши функции
    src->pub.init_source = JPEGInitSource;
    src->pub.fill_input_buffer = JPEGFillInputBuffer;
    src->pub.skip_input_data = JPEGSkipInputData;
    src->pub.resync_to_restart = jpeg_resync_to_restart; // use default method
    src->pub.term_source = JPEGTermSource;
    // теперь заполняем поля нашей структуры
    src->file = file;
    // настраиваем указатели на буфера
    src->pub.bytes_in_buffer = 0;  // forces fill_input_buffer on first read
    src->pub.next_input_byte = 0;  // until buffer loaded
    // !!! ВНИМАНИЕ !!! в библиотеке глючок, почему-то если infile стандартного менеджера
    // установлен в 0 то jpeg_start_decompress отказывается работать... поэтому установим его в 1 :)
    src->pub.infile = (FILE*)1;    // указатель на файл в стандартном менеджере, см. выше
    src->pub.buffer = 0;      // это буфер стандартного менеджера загрузки, мы им не пользуемся
  }

При работе библиотеки возможен случай (не в данном примере), когда читается несколько файлов один за другим с использованием одного экземпляра библиотеки. Именно для этого случая проверяется менеджер данных на существование. Если он еще не создан, то при помощи стандартного менеджера памяти выделяем под него место и устанавливаем на него указатель библиотеки. Заодно выделяем память под буфер данных. Беспокоиться об освобождении памяти не стоит, в дальнейшем за очистку данных будет отвечать менеджер памяти. Следующий шаг — настройка указателей библиотеки на переопределенные функции. И последний момент — инициализация данных. Она преподносит неприятный сюрприз: в стандартном менеджере данных, также содержится указатель на буфер данных (его можно обнулить)  и указатель на стандартный поток ввода вывода. Если эту переменную обнулить, то библиотека будет считать, что файл не открыт и естественно будет вылетать при первой же попытке к чтению.

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

  // Настраиваем обработку ошибок JPEG декомпрессором.
  cinfo.err = jpeg_std_error(&jerr.pub);    // устанавливаем дефолтный менеджер обработки ошибок
  jerr.pub.error_exit = JPEGErrorExit;    // присваиваем дефолтную функцию для обработки ошибки
  // Устанавливаем контекст возвращения setjmp на JPEGErrorExit
  if( setjmp(jerr.setjmp_buffer) ) {
    // когда-то произошла ошибка. Очистим структуру и закроем файл
    jpeg_destroy_decompress(&cinfo);
    file.Close();
    return( cDataManager::cError::InternalError );
  }

В случае ошибки программа вернется к условию и функция setjmp() вернет единичку. После настройки обработки ошибок инициализируем библиотеку, установим источник данных и считаем информацию о файле, которая запишется в структуру jpeg_decompress_struct.

  // инициализируем декомпрессионный объект
  jpeg_create_decompress(&cinfo);
  // Шаг 2: указываем источник данных
  JPEGStdioSrc(&cinfo, &file);
  // Шаг 3: читаем параметры JPEG файла
  jpeg_read_header(&cinfo, TRUE);

Начало декомпрессии файла. Вычисляется размер строки в байтах, выделяется память под картинку и под одну строку:

  // Шаг 5: начинаем декомпрессию
  jpeg_start_decompress(&cinfo);
  // вычисляем размер строки в байтах
  int row_stride = cinfo.output_width * cinfo.output_components;
  // выделяем память для картинки
  byte* pixels = new byte[cinfo.output_height * row_stride];
  // Выделяем память для одной строки. Но нужно помнить, что это массив строк, просто он состоит 
  // из одной строки :) JPOOL_IMAGE, означает, что память выделена пока читаем эту картинку
  JSAMPARRAY buffer = (*cinfo.mem->alloc_sarray)( (j_common_ptr)&cinfo, JPOOL_IMAGE, row_stride, 1 );
  memset(*buffer, 0, sizeof(JSAMPLE) * row_stride);

В вышеприведенном фрагменте, место для строки выделено менеджером памяти библиотеки. Освобождать его не надо — об этом будет заботиться сам менеджер. Начинаем построчно читать картинку, и не забываем, что она записана в перевернутом виде:

  // Шаг 6: считываем данные
  // Т.к. юзаем внутреннюю переменную cinfo.output_scanline, то не будем отслеживать сами себя
  int y = 0;
  while ( cinfo.output_scanline < cinfo.output_height ) {
    // читаем строку в буфер
    jpeg_read_scanlines(&cinfo, buffer, 1);
    // и вставляем эту строку на нужное место в массиве картинки
    // блин, они опять перевернули картинку! :(
    byte* temp = &pixels[ (cinfo.output_height - y - 1) * row_stride];
    memcpy(temp, buffer[0], row_stride);
    y++;
  }

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

  // Шаг 7: Завершаем декомпрессию файла
  jpeg_finish_decompress(&cinfo);
  // Шаг 8: Очищаем объект декомпрессии
  jpeg_destroy_decompress(&cinfo);
  // закрываем файл
  file.Close();

Заключение

Вот и все. Надеюсь, что чем-то смог вам помочь. С уважением, Мальцев Денис.

Исходный код к статье: 20040712.zip

Страницы: 1 2

#JPEG, #PNG, #ZIP, #файлы

12 июля 2004 (Обновление: 9 июня 2009)

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