Вернуться к разделу "DjVu-программы".
Консольная утилита c44 из состава пакета DjVuLibre предназначена для создания однослойных DjVu-файлов, содержащих только лишь задний фон (без маски и без переднего плана). Такой режим DjVu-файлов называется "DjVuPhoto" - потому что он наиболее качественно сохраняет многоцветное "фотографическое" изображение в формате DjVu (при этом используется вейвлетный алгоритм IW44).
Утилите с44 соответствует коммерческая программа phototodjvu из пакета Document Express Enterprise 5.1. Эти 2 программы (с44 и phototodjvu) ничем не отличаются в плане качества и свойств создаваемых ими DjVu-файлов. Обе они работают под Windows (а с44 ещё и под Linux; а также она имеет открытые исходники).
Однако у с44 есть такое неудобство, что она принимает на входе только лишь файлы в форматах PPM, PGM или JPG. Файлы любого другого формата приходится сначала преобразовывать в PPM или в PGM (при помощи Irfan View), и только потом конвертировать в DjVu посредством с44.
Для преодоления этого недостатка я создал новую утилиту на базе исходного кода с44 - fi_c44.
fi_c44 - это гибридная версия с44, которая использует свободно-бесплатную графическую библиотеку FreeImage для открытия графических файлов (подаваемых на вход fi_c44).
fi_c44 предназначена для работы только под ОС Windows (при желании можно сделать отдельно её Linux-версию - для Linux-версии FreeImage).
Библиотека FreeImage поддерживает более 30 графических форматов (на данный момент). Поэтому fi_c44 может принимать на входе любой из этих графических форматов (например TIF, BMP, JPEG-2000 и т.д.) и напрямую кодировать его в DjVuPhoto.
Кроме того, это повышает быстродействие использования с44-кодирования в скриптах: исчезает необходимость промежуточного преобразования "произвольный графический формат" -> PPM (PGM).
При этом fi_c44 сохраняет все прочие свойства с44 - т.е. все параметры и опции командной строки с44 остаются неизменными.
Правда, я добавил в fi_c44 опцию -bsf (Backround subsample factor), которая может принимать целочисленные значения от 2 до 12. Эта опция задаёт, во сколько раз fi_c44 следует уменьшить размеры создаваемого DjVu-файла по сравнению с кодируемым графическим файлом (аналогично csepdjvu). Опция bsf нужна, если предполагается вставка чанка BG44 из полученного DjVu-файла в другие DjVu-файлы.
Единственное ограничение, которое имеет fi_c44 по сравнению с с44 - это необходимость присутствия в зоне досягаемости fi_c44 файла FreeImage.dll. Без него fi_c44 не сможет работать.
Впрочем, на мой взгляд, это весьма несущественная проблема. Её можно рассматривать как "плату" за нововведённую мультиформатность. Файл FreeImage.dll имеет размер около 1,5-2 МБ и его можно скачать на сайте FreeImage.
Использование FreeImage даёт не только мультиформатность - FreeImage также предоставляет набор встроенных алгоритмов и удобный интерфейс для обработки загруженных растровых изображений. Т.е. графический файл можно не просто открыть и закодировать в DjVuPhoto - но также и как-либо обработать его нужным образом перед кодированием.
Готовую к работе утилиту fi_c44 можно скачать здесь (только для ОС Windows):
Скачать fi_c44 (вместе с исходником и FreeImage.dll) (546 КБ) |
fi_c44 принимает на входе только цветные 24-битные или серые 8-битные изображения (именно такие и подразумевает DjVuPhoto - остальные бессмысленно пытаться закодировать).
Синтаксис командной строки fi_c44 абсолютно идентичен синтаксису командной строки c44 (только вместо PPM-PGM файла указывается файл любого поддерживаемого FreeImage формата), за исключением добавленной новой опции:
- bsf n (2..12) - фактор уменьшения размеров создаваемого DjVu-файла (аналогично csepdjvu).
Утилита fi_c44 была создана мною под руководством Леона Боту (одного из создателей формата DjVu) и при помощи Андрея Жежеруна (автора программы WinDjView).
Я привожу ниже частичную выдержку из исходного кода fi_c44 (где показана лишь разница между с44 и fi_c44):
.... int flag_bsf = 1; .... void parse(GArray<GUTF8String> &argv) { ..... else if (argv[i] == "-bsf") { if (++i >= argc) G_THROW( ERR_MSG("fi_c44.no_bsf_arg") ); if (flag_bsf > 1) G_THROW( ERR_MSG("fi_c44.duplicate_bsf") ); char *ptr; flag_bsf = strtol(argv[i], &ptr, 10); if (*ptr || flag_bsf<2 || flag_bsf>12) G_THROW( ERR_MSG("fi_c44.illegal_bsf") ); } ... } //////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// // // WinDjView functions section // // Copyright (C) 2004-2009 Andrew Zhezherun // struct VersionInfo { VersionInfo(); bool bNT, b2kPlus, bXPPlus, bVistaPlus; }; static VersionInfo theVersionInfo; VersionInfo::VersionInfo() : bNT(false), b2kPlus(false), bXPPlus(false), bVistaPlus(false) { OSVERSIONINFO vi; vi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); if (::GetVersionEx(&vi)) { bNT = (vi.dwPlatformId == VER_PLATFORM_WIN32_NT); if (bNT) { b2kPlus = (vi.dwMajorVersion >= 5); bXPPlus = (vi.dwMajorVersion > 5 || vi.dwMajorVersion == 5 && vi.dwMinorVersion >= 1); bVistaPlus = (vi.dwMajorVersion >= 6); } } } //////////////////////////////////////////////////////////////////////////////////////////// bool IsWinXPOrLater() { return theVersionInfo.bXPPlus; } int ReadUTF8Character(const char* str, int& nBytes) { const unsigned char* s = reinterpret_cast<const unsigned char*>(str); unsigned char c = s[0]; if (c < 0x80) { nBytes = 1; return c; } else if (c < 0xC2) { return -1; } else if (c < 0xE0) { if (s[1] == 0 || (s[1] ^ 0x80) >= 0x40) return -1; nBytes = 2; return ((s[0] & 0x1F) << 6) + (s[1] ^ 0x80); } else if (c < 0xF0) { if (s[1] == 0 || s[2] == 0 || (s[1] ^ 0x80) >= 0x40 || (s[2] ^ 0x80) >= 0x40 || (c == 0xE0 && s[1] < 0xA0)) return -1; nBytes = 3; return ((s[0] & 0xF) << 12) + ((s[1] ^ 0x80) << 6) + (s[2] ^ 0x80); } else if (c < 0xF5) { if (s[1] == 0 || s[2] == 0 || s[3] == 0 || (s[1] ^ 0x80) >= 0x40 || (s[2] ^ 0x80) >= 0x40 || (s[3] ^ 0x80) >= 0x40 || (c == 0xF0 && s[1] < 0x90) || (c == 0xF4 && s[1] > 0x8F)) return -1; nBytes = 4; return ((s[0] & 0x07) << 18) + ((s[1] ^ 0x80) << 12) + ((s[2] ^ 0x80) << 6) + (s[3] ^ 0x80); } else return -1; } //////////////////////////////////////////////////////////////////////////////////////////// bool IsValidUTF8(const char* pszText) { const char* s = pszText; while (*s != 0) { int nBytes = 0; if (ReadUTF8Character(s, nBytes) < 0) return false; s += nBytes; } return true; } //////////////////////////////////////////////////////////////////////////////////////////// // // Convert GUTF8String to ASCII string: // // returns the lenght of the converted string // int MakeString(const GUTF8String& text, char* strResult) { if (text.length() == 0) return 0; // Prepare Unicode text LPWSTR pszUnicodeText = NULL; int nResult = 0; // Bookmarks or annotations may not be encoded in UTF-8 // (when file was created by old software) // Treat input string as non-UTF8 if it is not well-formed DWORD dwFlags = 0; // Only Windows XP supports checking for invalid characters in UTF8 encoding // inside MultiByteToWideChar function if (IsWinXPOrLater()) { dwFlags |= MB_ERR_INVALID_CHARS; } // Make our own check anyway if (!IsValidUTF8(text)) { strcpy(strResult,text); printf("%s\n","Not valid UTF8"); return 0; } int nSize = ::MultiByteToWideChar(CP_UTF8, dwFlags, (LPCSTR)text, -1, NULL, 0); if (nSize != 0) { pszUnicodeText = new WCHAR[nSize]; nResult = ::MultiByteToWideChar(CP_UTF8, dwFlags, (LPCSTR)text, -1, pszUnicodeText, nSize); } if (nResult != 0) { #ifdef _UNICODE // something may be implemented here (if needed) //strResult = pszUnicodeText; #else // Prepare ANSI text nSize = ::WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK | WC_DISCARDNS, pszUnicodeText, -1, NULL, 0, NULL, NULL); if (nSize == 0) { delete[] pszUnicodeText; return 0; } if (nSize > MAX_PATH) { delete[] pszUnicodeText; printf("Filepath is longer than %d symbols\n", MAX_PATH); return 0; } ::WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK | WC_DISCARDNS, pszUnicodeText, -1, strResult, nSize+1, NULL, NULL); #endif } else { strcpy(strResult,text); } delete[] pszUnicodeText; return strlen(strResult); } // End of the WinDjView functions section // ///////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// // FreeImage routines: //////////////////////////////////////////////////////////////////////////////// /** FreeImage error handler @param fif Format / Plugin responsible for the error @param message Error message */ void FreeImageErrorHandler(FREE_IMAGE_FORMAT fif, const char *message) { printf("\n*** "); printf("%s Format\n", FreeImage_GetFormatFromFIF(fif)); printf(message); printf(" ***\n"); } //////////////////////////////////////////////////////////////////////////////// /** Generic image loader @param lpszPathName Pointer to the full file name @param flag Optional load flag constant @return Returns the loaded dib if successful, returns NULL otherwise */ FIBITMAP* GenericLoader(const char* lpszPathName, int flag) { FREE_IMAGE_FORMAT fif = FIF_UNKNOWN; // check the file signature and deduce its format // (the second argument is currently not used by FreeImage) fif = FreeImage_GetFileType(lpszPathName, 0); FIBITMAP* dib; if(fif == FIF_UNKNOWN) { // no signature ? // try to guess the file format from the file extension fif = FreeImage_GetFIFFromFilename(lpszPathName); } // check that the plugin has reading capabilities ... if((fif != FIF_UNKNOWN) && FreeImage_FIFSupportsReading(fif)) { // ok, let's load the file dib = FreeImage_Load(fif, lpszPathName, flag); // unless a bad file format, we are done ! if (!dib) { printf("%s%s%s\n","File \"", lpszPathName, "\" not found."); return NULL; } } return dib; } ///////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////// // // Main function: // int main(int argc, char **argv) { setlocale(LC_ALL,""); djvu_programname(argv[0]); GArray<GUTF8String> dargv(0,argc-1); for(int i=0;i<argc;++i) dargv[i]=GNativeString(argv[i]); G_TRY { // Parse arguments parse(dargv); FreeImage_SetOutputMessage(FreeImageErrorHandler); char strResult[MAX_PATH] = {0}; MakeString(g().pnmurl.UTF8Filename(), strResult); // load a file into a universal FIBITMAP memory structure: FIBITMAP *dib; if (flag_bsf>1) { FIBITMAP *dib_tmp = GenericLoader(strResult, 0); unsigned width = FreeImage_GetWidth(dib_tmp); unsigned height = FreeImage_GetHeight(dib_tmp); dib = FreeImage_Rescale(dib_tmp, (int)((width+(flag_bsf-1))/flag_bsf), (int)((height+(flag_bsf-1))/flag_bsf), FILTER_BICUBIC); FreeImage_Unload(dib_tmp); } else dib = GenericLoader(LPCSTR(strResult), 0); if (FreeImage_GetImageType(dib) != FIT_BITMAP) { printf("Non-FIT_BITMAP images are not supported.\n"); FreeImage_Unload(dib); return -2; } if (!(FreeImage_GetBPP(dib) == 24) && !((FreeImage_GetBPP(dib) == 8) && (FreeImage_GetColorType(dib) != FIC_PALETTE))) { printf("Only 24-bit color or 8-bit greyscale images are supported.\n"); FreeImage_Unload(dib); return -3; } // Load images unsigned w = FreeImage_GetWidth(dib); // FIBITMAP width in pixels unsigned h = FreeImage_GetHeight(dib); // FIBITMAP height in pixels // Returns the width of the bitmap in bytes, rounded to the next 32-bit boundary, // also known as "pitch" or "stride" or "scan width". unsigned pitch = FreeImage_GetPitch(dib); BYTE* bits = (BYTE*)FreeImage_GetBits(dib); // the pointer to the 1 pixel // in the 1 pixel row (in FIBITMAP) BYTE* lines; unsigned bpp = FreeImage_GetBPP(dib); // bitdepth, 24 - for color, 8 - for greyscale unsigned btpp = bpp/8; GP<IW44Image> iw; //Just create a GPixmap of size w*h GP<GPixmap> gipm = GPixmap::create(h,w); //and fill it with the RGB data: unsigned i,j; for (j=0;j<h;j++) { GPixel *row = (*gipm)[j]; lines = bits + j * pitch; for (i=0; i<w; i++) { GPixel p; if (btpp == 3) // color 24 bit { p.r = lines[FI_RGBA_RED]; // red byte p.g = lines[FI_RGBA_GREEN]; // green byte p.b = lines[FI_RGBA_BLUE]; // blue byte row[i] = p; lines += btpp; } else if (btpp == 1) // greyscale 8 bit { p.r = lines[i]; // red byte p.g = lines[i]; // green byte p.b = lines[i]; // blue byte row[i] = p; } } } GPixmap &ipm=*gipm; int bm_size = w*h*btpp; // Change percent specification into size specification if (flag_size && flag_percent) for (int i=0; i<argc_size; i++) argv_size[i] = (argv_size[i]*bm_size)/ 100; flag_percent = 0; iw = IW44Image::create_encode(ipm, getmask(w,h), arg_crcbmode); // Perform compression PM44 or BM44 as required if (iw) { g().iw4url.deletefile(); GP<IFFByteStream> iff = IFFByteStream::create(ByteStream::create(g().iw4url,"wb")); if (flag_crcbdelay >= 0) iw->parm_crcbdelay(flag_crcbdelay); if (flag_dbfrac > 0) iw->parm_dbfrac((float)flag_dbfrac); int nchunk = resolve_quality(w*h); // Create djvu file create_photo_djvu_file(*iw, w, h, *iff, nchunk, g().parms); } FreeImage_Unload(dib); } G_CATCH(ex) { ex.perror(); exit(1); } G_ENDCATCH; return 0; } //////////////////////////////////////////////////////////////////////////////////////////// |
Этот код включает в себя 2 интересных момента:
(Создано с помощью Андрея Жежеруна)
char strResult[MAX_PATH] = {0}; MakeString(g().pnmurl.UTF8Filename(), strResult); // load a file into a universal FIBITMAP memory structure: FIBITMAP *dib = GenericLoader(strResult, 0); |
Этот механизм целиком базируется на использовании функций, взятых из программы WinDjView. Функция MakeString - это слегка преобразованная функция MakeСString из исходников WinDjView.
Программа считывает путь к кодируемому файлу из командной строки и помещает его в объект класса GURL в виде URL-encoded строки.
Метод UTF8Filename класса GURL преобразует URL-encoded строку в объект GUTF8String (т.е. строка в кодировке UTF8 (Unicode).
Функция MakeString (взятая из исходников WinDjView) преобразует UTF8-строку в обычную ASCII-cтроку, которая затем используется для открытия графического файла функциями FreeImage.
В библиотеке FreeImage есть также Unicode-версии функций, открывающих файлы - поэтому последняя операция преобразования UTF8 -> ASCII, строго говоря, не обязательна. Однако, Unicode-режим вообще плохо поддерживается, например, в Windows 98 - поэтому, на мой взгляд, в данном случае надежнее работать с обычными ASCII-строками.
Примечание: Для преобразования UTF8 -> ASCII используется буфер strResult с фиксированной длиной 260 символов. В данном случае фиксированная длина буфера допускается (хотя обычно выделяется динамический массив переменной длины, размер которого определяется заранее). Дело в том, что Windows имеет максимальное ограничение длины файлового пути в 260 символов (задаётся библиотечной константой MAX_PATH). Подробнее см. в Википедии тут: Имя файла.
(Создано под руководством Леона Боту)
//Just
create a GPixmap of size w*h GP<GPixmap> gipm = GPixmap::create(h,w); //and fill it with the RGB data: unsigned i,j; for (j=0;j<h;j++) { GPixel *row = (*gipm)[j]; lines = bits + j * pitch; for (i=0; i<w; i++) { GPixel p; if (btpp == 3) // color 24 bit { p.r = lines[FI_RGBA_RED]; // red byte p.g = lines[FI_RGBA_GREEN]; // green byte p.b = lines[FI_RGBA_BLUE]; // blue byte row[i] = p; |
Библиотека FreeImage внутренне использует универсальную рабочую структуру FIBITMAP.
FIBITMAP - это стандартный "контейнер" в памяти, в который (у FreeImage) распаковывается загруженное с диска изображение из файла любого поддерживаемого формата. Далее FreeImage работает уже с этим FIBITMAP - а не с самим исходным файлом. После окончания обработки готовое изображение выгружается из FIBITMAP на диск (в файл любого поддерживаемого графического формата).
Такой подход обеспечивает универсальность обработки и расширяемость использования самых разнообразных графических форматов в библиотеке FreeImage.
FIBITMAP с программной точки зрения идентичен стандартной Windows-структуре для работы с растровыми данными, которая называется "DIB" (Device independent bitmap). Подробнее про DIB см. в Википедии тут: Device-independent_bitmap. Это было сделано для того, чтобы дополнить стандартную функциональность Windows возможностями FreeImage (в отношении работы с растровыми иображениями).
Одной из особенностей FIBITMAP является дополнение каждой строки пикселей нулями справа до ближайшей 32-битной границы (т.е. выравнивание строк пикселей на границы двойных слов; a ещё точно так же выравнивается и начало пиксельной таблицы). Это предназначено для ускорения обработки растровых изображений - т.к. Windows считывает их сразу блоками, кратными 32-битам.
Форматы PPM и PGM не имеют такого выравнивания. Они представляют из себя, грубо говоря, просто прямоугольные таблицы пикселей. Это происходит от того, что форматы PPM и PGM были рождены в мире Linux - где такие Windows-понятия, как "DIB", не имеют никакого смысла.
Библиотека DjVuLibre с самого начала пошла по "лёгкому" "пути Linux" - и стала использовать форматы PPM и PGM как базовые (потому что с простыми прямоугольными таблицами пикселей легче и быстрее работать). Так что в DjVuLibre, так же как и в PPM-PGM, отсутствует 32-битное выравнивание для пиксельных строк.
Поэтому прямое использование FIBITMAP в DjVuLibre невозможно - приходится сначала делать преобразование из 32-выровненного FIBITMAP в 32-невыровненную структуру GPixmap (одна из внутренних структур DjVuLibre) и дальше работать уже с GPixmap.
Автор: monday2000.
5 июня 2009 г.
E-Mail (monday2000 [at] yandex.ru)