Преамбула
На одном ресурсе со сканированными книгами встала проблема со скачиванием с него файлов и продажей их на платных ресурсах. Встала необходимость вставлять свой логотип в файлы djvu, так как большинство книг именно в этом формате. Однако перекодирование при помощи DjvuLibre приводило к ухудшению качества изображения и увеличению размера файла. Других инструментов для перекодирования под Linux на тот момент найдено не было.
Краткий экскурс в формат
Формат DJVU был разработан компанией AT&T для сканированных книг. Кроме изображений страниц он содержит meta-информацию, такую как распознанный текст, аннотация и гиперссылки. Для кодирования изображений могут использоваться разнообразные алгоритмы сжатия. Наиболее распространено сжатие, когда изображение разделяется на два слоя — background (фон) и foreground (обычно текст). Причем фон сжимается с более низким разрешением методом сжатия с потерями, а основной информативный слой кодируется сжатием без потерь и с высоким разрешением.
![]() |
Исходное изображение |
На изображениях ниже можно увидеть эти слои по отдельности
![]() |
![]() |
Foreground |
Background |
Выбор решения
Поигравшись с полным перекодированием, я пришел к выводу, что довольно сложно в автоматическом режиме добиться хорошего качества результата. Это навело меня на мысль вставлять логотип в foreground, а фон не трогать.
![]() |
Логотип для вставки |
Суть решения простая (приводится для одной страницы):
- Сохраняем всю meta-информацию
- Вытаскиваем только foreground
- Вставляем логотип
- Кодируем foreground в djvu
- Заменяем старый foreground на новый
![]() |
![]() |
Foreground с наложенным логотипом |
Результат |
Вроде все просто, да и инструменты все есть.
Решение
Для решения задачи я использовал Perl, DjvuLibre и ImageMagick, директории их исполняемых файлов были занесены в PATH для простоты работы
Итоговый скрипт на Perl, принимается что все изображения без background черно-белые (1 бит на пиксель):
my $path='c:\\djvu_logo\\temp\\'; # Путь к временным файлам
my $dirtemp = 'c:\\djvu_logo\\temp';
my $logo_color = '.\\data\\logo_color.bmp';
my $logo_mono = '.\\data\\logo_mono.bmp';
my $filename = shift(@ARGV);
if ($filename ne ''){
#### Сохранение файла
my $infilnam = $dirtemp.'\\in.djvu';
use File::Copy;
copy($filename, $infilnam);
####
my $file_name_out = $dirtemp.'/out.djvu';
copy($infilnam, $file_name_out);
#### Определяем количество страниц
my $pages='';
open(ODIN, "djvused $infilnam -e n|");
while (<ODIN>){
$pages=$_;
}
close(ODIN);
$pages=$pages*1;
print "\nКоличество страниц = $pages \n";
if ($pages<1){
die('Не могу обработать файл');
} else {
### В какие страницы вставлять. Здесь - в каждую 20-ю
my $ii=0;
my @page_to=();
for ($ii=1;$ii<$pages;$ii=$ii+20){
push (@page_to,$ii);
}
push (@page_to,$pages);
my $n_to;
for ($n_to=0;$n_to<=$#page_to;$n_to++){
### Цикл по страницам
my $background;
my $BG;
my $page_num = $page_to[$n_to];
$page_num=$page_num*1;
### Сохранение мета-информации
my $file_all = $dirtemp.'\\tmp_all.dsed';
my $file_name_djvused=$dirtemp.'\\djvused_temp.dsed';
open(DVA, "> $file_name_djvused");
print DVA "select $page_num \n output-all";
close(DVA);
my $txt_all='';
open(DT, "djvused $infilnam -e \"select $page_num; output-all\" |");
while(<DT>){$txt_all.=$_;}
close(DT);
open(DVA, "> $file_all");
print DVA "select 1 \n".$txt_all."\n save";
close(DVA);
### Конец создания файла с мета-информацией
my $param_page;
open(TRI, "djvused $infilnam -e \"select $page_num; dump\" |");
while (<TRI>){
$param_page .= $_;
}
close(TRI);
#### Разбор параметров
my @lines= split(/\n/,$param_page);
my @opt_to_rc=();
shift (@lines);
shift (@lines);
foreach(@lines){
my @lin=split(/ +/,$_);
shift(@lin);
if ($lin[0] ne "INFO"){
$a=0;
foreach (@opt_to_rc){
if ($lin[0] eq $_){
$a=1;
}
}
if ($a==0){
push(@opt_to_rc, $lin[0]);
}
}
}
###
$background='NO';
foreach (@opt_to_rc){
if (substr($_,0,2) eq 'BG'){
$background='YES';
$BG=$_;
}
}
### Если имеется бэкграунд
my $file_name_in=$infilnam;
if ($background eq 'YES'){
my $file_ppm=$dirtemp."/tmp_ppm.ppm";
my $file_ppm_stamp=$dirtemp."/tmp_ppm_stamp.ppm";
my $file_ppm_djvu=$dirtemp."/tmp_ppm.djvu";
my $file_FGbz=$dirtemp."/tmp_FGbz.djvu";
my $file_Sjbz=$dirtemp."/tmp_Sjbz.djvu";
my $file_BG=$dirtemp."/tmp_BG.djvu";
print "\nБэкграунд есть\n";
print("Извлечения верхнего слоя в картинку\n");
system("ddjvu -format=ppm -mode=foreground -page=".$page_num." ".$file_name_in." ".$file_ppm);
print("Извлечение бэкграунда\n");
system("djvuextract ".$file_name_in." -page=".$page_num." ".$BG."=".$file_BG);
print("Наложение на верхний слой логотипа\n");
system("convert ".$file_ppm." ".$logo_color." -gravity SouthEast -composite ".$file_ppm_stamp);
print("Перевод картинки с логотипом в DjVu\n");
system("cpaldjvu -bgwhite ".$file_ppm_stamp." ".$file_ppm_djvu);
print("Извлечение из картинки с логотипом маски и бэкграунда\n");
system("djvuextract ".$file_ppm_djvu." Sjbz=".$file_Sjbz." FGbz=".$file_FGbz);
print("Собирание нового DjVu файла\n");
system("djvumake ".$dirtemp."/dsed.djvu Sjbz=".$file_Sjbz." FGbz=".$file_FGbz." ".$BG."=".$file_BG);
print ("\n");
} else {
### Если нет бэкграунда
print "\nБэкграунда нет\n";
my $file_pbm=$dirtemp."/tmp_pbm.pbm";
my $file_pbm_stamp=$dirtemp."/tmp_stamp.pbm";
my $file_cjb2=$dirtemp."/tmp_stamp_b.djvu";
print "Извлечение маски в картинку\n";
system("ddjvu -format=pbm -mode=mask -page=".$page_num." ".$file_name_in." ".$file_pbm);
print "Наложение логотипа\n";
system("composite -monochrome -gravity SouthEast -compose bumpmap $logo_mono $file_pbm $file_pbm_stamp");
print "Компилирование нового DjVu\n";
system("cjb2 ".$file_pbm_stamp." ".$dirtemp."/dsed.djvu");
}
### Общее для обоих случаев
print "Вставляем текст обратно в страницу\n";
system("djvused ".$dirtemp."/dsed.djvu -f ".$file_all);
print "Вставляем новую страницу в выходной файл\n";
system("djvm -i ".$file_name_out." ".$dirtemp."/dsed.djvu ".$page_num);
$page_num++;
print "Удаляем старую страницу из выходного файла\n";
system("djvm -d ".$file_name_out." ".$page_num);
}
}
}