Вернуться к разделу "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)