The website "djvu-soft.narod.ru." is not registered with uCoz.
If you are absolutely sure your website must be here,
please contact our Support Team.
If you were searching for something on the Internet and ended up here, try again:

About uCoz web-service

Community

Legal information

Консольная утилита fi_c44

Вернуться к разделу "DjVu-программы".


Консольная утилита fi_c44

Введение

Консольная утилита 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 интересных момента:

1. Преобразование пути:

(Создано с помощью Андрея Жежеруна)

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). Подробнее см. в Википедии тут: Имя файла.

2. Загрузка изображения через FreeImage:

(Создано под руководством Леона Боту)

//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;
    }
}

Библиотека 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)

Hosted by uCoz