Как стать автором
Обновить

Вставка логотипа в djvu-файлы без полного перекодирования

Преамбула

На одном ресурсе со сканированными книгами встала проблема со скачиванием с него файлов и продажей их на платных ресурсах. Встала необходимость вставлять свой логотип в файлы djvu, так как большинство книг именно в этом формате. Однако перекодирование при помощи DjvuLibre приводило к ухудшению качества изображения и увеличению размера файла. Других инструментов для перекодирования под Linux на тот момент найдено не было.



Краткий экскурс в формат

Формат DJVU был разработан компанией AT&T для сканированных книг. Кроме изображений страниц он содержит meta-информацию, такую как распознанный текст, аннотация и гиперссылки. Для кодирования изображений могут использоваться разнообразные алгоритмы сжатия. Наиболее распространено сжатие, когда изображение разделяется на два слоя — background (фон) и foreground (обычно текст). Причем фон сжимается с более низким разрешением методом сжатия с потерями, а основной информативный слой кодируется сжатием без потерь и с высоким разрешением.


Исходное изображение

На изображениях ниже можно увидеть эти слои по отдельности



Foreground
Background

Выбор решения

Поигравшись с полным перекодированием, я пришел к выводу, что довольно сложно в автоматическом режиме добиться хорошего качества результата. Это навело меня на мысль вставлять логотип в foreground, а фон не трогать.


Логотип для вставки

Суть решения простая (приводится для одной страницы):

  1. Сохраняем всю meta-информацию
  2. Вытаскиваем только foreground
  3. Вставляем логотип
  4. Кодируем foreground в djvu
  5. Заменяем старый 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);
      }
    }
  }
 

Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.