Blame tools/cpaldjvu.cpp

Packit df99a1
//C-  -*- C++ -*-
Packit df99a1
//C- -------------------------------------------------------------------
Packit df99a1
//C- DjVuLibre-3.5
Packit df99a1
//C- Copyright (c) 2002  Leon Bottou and Yann Le Cun.
Packit df99a1
//C- Copyright (c) 2001  AT&T
Packit df99a1
//C-
Packit df99a1
//C- This software is subject to, and may be distributed under, the
Packit df99a1
//C- GNU General Public License, either Version 2 of the license,
Packit df99a1
//C- or (at your option) any later version. The license should have
Packit df99a1
//C- accompanied the software or you may obtain a copy of the license
Packit df99a1
//C- from the Free Software Foundation at http://www.fsf.org .
Packit df99a1
//C-
Packit df99a1
//C- This program is distributed in the hope that it will be useful,
Packit df99a1
//C- but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit df99a1
//C- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit df99a1
//C- GNU General Public License for more details.
Packit df99a1
//C- 
Packit df99a1
//C- DjVuLibre-3.5 is derived from the DjVu(r) Reference Library from
Packit df99a1
//C- Lizardtech Software.  Lizardtech Software has authorized us to
Packit df99a1
//C- replace the original DjVu(r) Reference Library notice by the following
Packit df99a1
//C- text (see doc/lizard2002.djvu and doc/lizardtech2007.djvu):
Packit df99a1
//C-
Packit df99a1
//C-  ------------------------------------------------------------------
Packit df99a1
//C- | DjVu (r) Reference Library (v. 3.5)
Packit df99a1
//C- | Copyright (c) 1999-2001 LizardTech, Inc. All Rights Reserved.
Packit df99a1
//C- | The DjVu Reference Library is protected by U.S. Pat. No.
Packit df99a1
//C- | 6,058,214 and patents pending.
Packit df99a1
//C- |
Packit df99a1
//C- | This software is subject to, and may be distributed under, the
Packit df99a1
//C- | GNU General Public License, either Version 2 of the license,
Packit df99a1
//C- | or (at your option) any later version. The license should have
Packit df99a1
//C- | accompanied the software or you may obtain a copy of the license
Packit df99a1
//C- | from the Free Software Foundation at http://www.fsf.org .
Packit df99a1
//C- |
Packit df99a1
//C- | The computer code originally released by LizardTech under this
Packit df99a1
//C- | license and unmodified by other parties is deemed "the LIZARDTECH
Packit df99a1
//C- | ORIGINAL CODE."  Subject to any third party intellectual property
Packit df99a1
//C- | claims, LizardTech grants recipient a worldwide, royalty-free, 
Packit df99a1
//C- | non-exclusive license to make, use, sell, or otherwise dispose of 
Packit df99a1
//C- | the LIZARDTECH ORIGINAL CODE or of programs derived from the 
Packit df99a1
//C- | LIZARDTECH ORIGINAL CODE in compliance with the terms of the GNU 
Packit df99a1
//C- | General Public License.   This grant only confers the right to 
Packit df99a1
//C- | infringe patent claims underlying the LIZARDTECH ORIGINAL CODE to 
Packit df99a1
//C- | the extent such infringement is reasonably necessary to enable 
Packit df99a1
//C- | recipient to make, have made, practice, sell, or otherwise dispose 
Packit df99a1
//C- | of the LIZARDTECH ORIGINAL CODE (or portions thereof) and not to 
Packit df99a1
//C- | any greater extent that may be necessary to utilize further 
Packit df99a1
//C- | modifications or combinations.
Packit df99a1
//C- |
Packit df99a1
//C- | The LIZARDTECH ORIGINAL CODE is provided "AS IS" WITHOUT WARRANTY
Packit df99a1
//C- | OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
Packit df99a1
//C- | TO ANY WARRANTY OF NON-INFRINGEMENT, OR ANY IMPLIED WARRANTY OF
Packit df99a1
//C- | MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
Packit df99a1
//C- +------------------------------------------------------------------
Packit df99a1
Packit df99a1
#ifdef HAVE_CONFIG_H
Packit df99a1
# include "config.h"
Packit df99a1
#endif
Packit df99a1
#if NEED_GNUG_PRAGMAS
Packit df99a1
# pragma implementation
Packit df99a1
#endif
Packit df99a1
Packit df99a1
/** @name cpaldjvu
Packit df99a1
Packit df99a1
    {\bf Synopsis}
Packit df99a1
    \begin{verbatim}
Packit df99a1
        cpaldjvu [options] <inputppmfile> <outputdjvufile>
Packit df99a1
    \end{verbatim}
Packit df99a1
Packit df99a1
    {\bf Description}
Packit df99a1
    
Packit df99a1
    File #"cpaldjvu.cpp"# demonstrates a simple quasi-lossless compressor for
Packit df99a1
    low resolution, low color, images with a reduced number of colors (e.g
Packit df99a1
    screendumps).  It simply quantizes the image on a limited number of
Packit df99a1
    colors, uses the dominant color to construct a uniform background, then
Packit df99a1
    performs lossless jb2 compression for all remaining objects.  
Packit df99a1
    
Packit df99a1
    Options
Packit df99a1
    \begin{description}
Packit df99a1
    \item[-colors n]  Maximum number of colors during quantization (default 256)
Packit df99a1
    \item[-dpi n]     Resolution written into the output file (default 100).
Packit df99a1
    \item[-verbose]   Displays additional messages.
Packit df99a1
    \end{description}
Packit df99a1
Packit df99a1
    {\bf Remarks}
Packit df99a1
Packit df99a1
    This is an interesting alternative to GIF. It performs especially well on
Packit df99a1
    screendumps.  Compression ratios can get hurt when there are continuous
Packit df99a1
    tone segment in the image.  Demoting such segments from foreground to
Packit df99a1
    background is a pretty interesting project.  Dithered segments behave
Packit df99a1
    surprisingly well.
Packit df99a1
Packit df99a1
    @memo
Packit df99a1
    Simple encoder for low resolution, low color images.
Packit df99a1
    @author
Packit df99a1
    L\'eon Bottou <leonb@research.att.com>
Packit df99a1
*/
Packit df99a1
//@{
Packit df99a1
//@}
Packit df99a1
Packit df99a1
Packit df99a1
#include "DjVuGlobal.h"
Packit df99a1
#include "GException.h"
Packit df99a1
#include "GSmartPointer.h"
Packit df99a1
#include "GContainer.h"
Packit df99a1
#include "ByteStream.h"
Packit df99a1
#include "IFFByteStream.h"
Packit df99a1
#include "GRect.h"
Packit df99a1
#include "GBitmap.h"
Packit df99a1
#include "JB2Image.h"
Packit df99a1
#include "DjVuPalette.h"
Packit df99a1
#include "IW44Image.h"
Packit df99a1
#include "DjVuInfo.h"
Packit df99a1
#include "GOS.h"
Packit df99a1
#include "GURL.h"
Packit df99a1
#include "DjVuMessage.h"
Packit df99a1
#include "jb2tune.h"
Packit df99a1
#include "common.h"
Packit df99a1
Packit df99a1
#ifdef MIN
Packit df99a1
#undef MIN
Packit df99a1
#endif
Packit df99a1
#ifdef MAX
Packit df99a1
#undef MAX
Packit df99a1
#endif
Packit df99a1
inline int MIN(int a, int b) { return ( a
Packit df99a1
inline int MAX(int a, int b) { return ( a>b ?a :b); }
Packit df99a1
Packit df99a1
Packit df99a1
// --------------------------------------------------
Packit df99a1
// COLOR CONNECTED COMPONENT ANALYSIS
Packit df99a1
// --------------------------------------------------
Packit df99a1
Packit df99a1
// -- A run of pixels with the same color
Packit df99a1
struct Run    
Packit df99a1
{ 
Packit df99a1
  short y;       // vertical coordinate
Packit df99a1
  short x1;      // first horizontal coordinate
Packit df99a1
  short x2;      // last horizontal coordinate
Packit df99a1
  short color;   // color id
Packit df99a1
  int ccid;      // component id
Packit df99a1
};
Packit df99a1
Packit df99a1
Packit df99a1
// -- A component descriptor
Packit df99a1
struct CC    
Packit df99a1
{
Packit df99a1
  GRect bb;      // bounding box
Packit df99a1
  int npix;      // number of black pixels
Packit df99a1
  int nrun;      // number of runs
Packit df99a1
  int frun;      // first run in cc ordered array of runs
Packit df99a1
  int color;     // color id
Packit df99a1
};
Packit df99a1
Packit df99a1
Packit df99a1
// -- An image composed of runs
Packit df99a1
class CCImage 
Packit df99a1
{
Packit df99a1
public:
Packit df99a1
  int height;            // Height of the image in pixels
Packit df99a1
  int width;             // Width of the image in pixels
Packit df99a1
  GTArray<Run> runs;     // Array of runs
Packit df99a1
  GTArray<CC>  ccs;      // Array of component descriptors
Packit df99a1
  int nregularccs;       // Number of regular ccs (set by merge_and_split_ccs)
Packit df99a1
  CCImage(int width, int height);
Packit df99a1
  void add_single_run(int y, int x1, int x2, int color, int ccid=0);
Packit df99a1
  GP<GBitmap> get_bitmap_for_cc(int ccid) const;
Packit df99a1
  void make_ccids_by_analysis();
Packit df99a1
  void make_ccs_from_ccids();
Packit df99a1
  void merge_and_split_ccs(int smallsize, int largesize);
Packit df99a1
  void sort_in_reading_order(); 
Packit df99a1
  void erase_cc(int ccid);
Packit df99a1
};
Packit df99a1
Packit df99a1
Packit df99a1
// -- Compares runs
Packit df99a1
static inline bool
Packit df99a1
operator <= (const Run &a, const Run &b)
Packit df99a1
{
Packit df99a1
  return (a.y
Packit df99a1
}
Packit df99a1
Packit df99a1
Packit df99a1
// -- Constructs CCImage and provide defaults
Packit df99a1
CCImage::CCImage(int width, int height)
Packit df99a1
  : height(height), width(width), nregularccs(0)
Packit df99a1
{
Packit df99a1
}
Packit df99a1
Packit df99a1
Packit df99a1
// -- Adds a run to the CCImage
Packit df99a1
inline void 
Packit df99a1
CCImage::add_single_run(int y, int x1, int x2, int color, int ccid)
Packit df99a1
{
Packit df99a1
  int index = runs.hbound();
Packit df99a1
  runs.touch(++index);
Packit df99a1
  Run& run = runs[index];
Packit df99a1
  run.y = y;
Packit df99a1
  run.x1 = x1;
Packit df99a1
  run.x2 = x2;
Packit df99a1
  run.color = color;
Packit df99a1
  run.ccid = ccid;
Packit df99a1
  
Packit df99a1
}
Packit df99a1
Packit df99a1
Packit df99a1
// -- Performs color connected component analysis
Packit df99a1
void
Packit df99a1
CCImage::make_ccids_by_analysis()
Packit df99a1
{
Packit df99a1
  // Sort runs
Packit df99a1
  runs.sort();
Packit df99a1
  // Single Pass Connected Component Analysis (with unodes)
Packit df99a1
  int n;
Packit df99a1
  int p=0;
Packit df99a1
  GTArray<int> umap;
Packit df99a1
  for (n=0; n<=runs.hbound(); n++)
Packit df99a1
    {
Packit df99a1
      int y = runs[n].y;
Packit df99a1
      int x1 = runs[n].x1 - 1;
Packit df99a1
      int x2 = runs[n].x2 + 1;
Packit df99a1
      int color = runs[n].color;
Packit df99a1
      int id = (umap.hbound() + 1);
Packit df99a1
      // iterate over previous line runs
Packit df99a1
      if (p>0) p--;
Packit df99a1
      for(;runs[p].y < y-1;p++);
Packit df99a1
      for(;(runs[p].y < y) && (runs[p].x1 <= x2);p++ )
Packit df99a1
        {
Packit df99a1
          if ( runs[p].x2 >= x1 )
Packit df99a1
            {
Packit df99a1
              if (runs[p].color == color)
Packit df99a1
                {
Packit df99a1
                  // previous run touches current run and has same color
Packit df99a1
                  int oid = runs[p].ccid;
Packit df99a1
                  while (umap[oid] < oid)
Packit df99a1
                    oid = umap[oid];
Packit df99a1
                  if ((int)id > umap.hbound()) {
Packit df99a1
                    id = oid;
Packit df99a1
                  } else if (id < oid) {
Packit df99a1
                    umap[oid] = id;
Packit df99a1
                  } else {
Packit df99a1
                    umap[id] = oid;
Packit df99a1
                    id = oid;
Packit df99a1
                  }
Packit df99a1
                  // freshen previous run id
Packit df99a1
                  runs[p].ccid = id;
Packit df99a1
                }
Packit df99a1
              // stop if previous run goes past current run
Packit df99a1
              if (runs[p].x2 >= x2)
Packit df99a1
                break;
Packit df99a1
            }
Packit df99a1
        }
Packit df99a1
      // create new entry in umap
Packit df99a1
      runs[n].ccid = id;
Packit df99a1
      if (id > umap.hbound())
Packit df99a1
        {
Packit df99a1
          umap.touch(id);
Packit df99a1
          umap[id] = id;
Packit df99a1
        }
Packit df99a1
    }
Packit df99a1
  // Update umap and ccid
Packit df99a1
  for (n=0; n<=runs.hbound(); n++)
Packit df99a1
    {
Packit df99a1
      Run &run = runs[n];
Packit df99a1
      int ccid = run.ccid;
Packit df99a1
      while (umap[ccid] < ccid)
Packit df99a1
        ccid = umap[ccid];
Packit df99a1
      umap[run.ccid] = ccid;
Packit df99a1
      run.ccid = ccid;
Packit df99a1
    }
Packit df99a1
}
Packit df99a1
Packit df99a1
Packit df99a1
// -- Constructs the ``ccs'' array from run's ccids.
Packit df99a1
void
Packit df99a1
CCImage::make_ccs_from_ccids()
Packit df99a1
{
Packit df99a1
  int n;
Packit df99a1
  Run *pruns = runs;
Packit df99a1
  // Find maximal ccid
Packit df99a1
  int maxccid = -1;
Packit df99a1
  for (n=0; n<=runs.hbound(); n++)
Packit df99a1
    if (pruns[n].ccid > maxccid)
Packit df99a1
      maxccid = runs[n].ccid;
Packit df99a1
  GTArray<int> armap(0,maxccid);
Packit df99a1
  int *rmap = armap;
Packit df99a1
  // Renumber ccs 
Packit df99a1
  for (n=0; n<=maxccid; n++)
Packit df99a1
    armap[n] = -1;
Packit df99a1
  for (n=0; n<=runs.hbound(); n++)
Packit df99a1
    if (pruns[n].ccid >= 0)
Packit df99a1
      rmap[ pruns[n].ccid ] = 1;
Packit df99a1
  int nid = 0;
Packit df99a1
  for (n=0; n<=maxccid; n++)
Packit df99a1
    if (rmap[n] > 0)
Packit df99a1
      rmap[n] = nid++;
Packit df99a1
  // Adjust nregularccs (since ccs are renumbered)
Packit df99a1
  while (nregularccs>0 && rmap[nregularccs-1]<0)
Packit df99a1
    nregularccs -= 1;
Packit df99a1
  if (nregularccs>0)
Packit df99a1
    nregularccs = 1 + rmap[nregularccs-1];
Packit df99a1
  // Prepare cc descriptors
Packit df99a1
  ccs.resize(0,nid-1);
Packit df99a1
  for (n=0; n
Packit df99a1
    ccs[n].nrun = 0;
Packit df99a1
  // Relabel runs
Packit df99a1
  for (n=0; n<=runs.hbound(); n++)
Packit df99a1
    {
Packit df99a1
      Run &run = pruns[n];
Packit df99a1
      if (run.ccid < 0) continue;  // runs with negative ccids are destroyed
Packit df99a1
      int oldccid = run.ccid;
Packit df99a1
      int newccid = rmap[oldccid];
Packit df99a1
      CC &cc = ccs[newccid];
Packit df99a1
      run.ccid = newccid;
Packit df99a1
      cc.nrun += 1;
Packit df99a1
    }
Packit df99a1
  // Compute positions for runs of cc
Packit df99a1
  int frun = 0;
Packit df99a1
  for (n=0; n
Packit df99a1
    {
Packit df99a1
      ccs[n].frun = rmap[n] = frun;
Packit df99a1
      frun += ccs[n].nrun;
Packit df99a1
    }
Packit df99a1
  // Copy runs
Packit df99a1
  GTArray<Run> rtmp;
Packit df99a1
  rtmp.steal(runs);
Packit df99a1
  Run *ptmp = rtmp;
Packit df99a1
  runs.resize(0,frun-1);
Packit df99a1
  pruns = runs;
Packit df99a1
  for (n=0; n<=rtmp.hbound(); n++)
Packit df99a1
    {
Packit df99a1
      int id = ptmp[n].ccid;
Packit df99a1
      if (id < 0) continue;
Packit df99a1
      int pos = rmap[id]++;
Packit df99a1
      pruns[pos] = ptmp[n];
Packit df99a1
    }
Packit df99a1
  // Finalize ccs
Packit df99a1
  for (n=0; n
Packit df99a1
    {
Packit df99a1
      CC &cc = ccs[n];
Packit df99a1
      int npix = 0;
Packit df99a1
      runs.sort(cc.frun, cc.frun+cc.nrun-1);
Packit df99a1
      Run *run = &runs[cc.frun];
Packit df99a1
      int xmin = run->x1;
Packit df99a1
      int xmax = run->x2;
Packit df99a1
      int ymin = run->y;
Packit df99a1
      int ymax = run->y;
Packit df99a1
      cc.color = run->color;
Packit df99a1
      for (int i=0; i
Packit df99a1
        {
Packit df99a1
          if (run->x1 < xmin)  xmin = run->x1;
Packit df99a1
          if (run->x2 > xmax)  xmax = run->x2;
Packit df99a1
          if (run->y  < ymin)  ymin = run->y;
Packit df99a1
          if (run->y  > ymax)  ymax = run->y;
Packit df99a1
          npix += run->x2 - run->x1 + 1;
Packit df99a1
        }
Packit df99a1
      cc.npix = npix;
Packit df99a1
      cc.bb.xmin = xmin;
Packit df99a1
      cc.bb.ymin = ymin;
Packit df99a1
      cc.bb.xmax = xmax + 1;
Packit df99a1
      cc.bb.ymax = ymax + 1;
Packit df99a1
    }
Packit df99a1
}
Packit df99a1
Packit df99a1
Packit df99a1
// -- Helper for merge_and_split_ccs
Packit df99a1
struct Grid_x_Color 
Packit df99a1
{
Packit df99a1
  short gridi;
Packit df99a1
  short gridj;
Packit df99a1
  int color;
Packit df99a1
};
Packit df99a1
Packit df99a1
Packit df99a1
// -- Helper for merge_and_split_ccs
Packit df99a1
static inline unsigned int
Packit df99a1
hash(const Grid_x_Color &x) 
Packit df99a1
{
Packit df99a1
  return (x.gridi<<16) ^ (x.gridj<<8) ^ x.color;
Packit df99a1
}
Packit df99a1
Packit df99a1
Packit df99a1
// -- Helper for merge_and_split_ccs
Packit df99a1
static inline bool
Packit df99a1
operator==(const Grid_x_Color &x, const Grid_x_Color &y)
Packit df99a1
{
Packit df99a1
  return (x.gridi==y.gridi) && (x.gridj==y.gridj) && (x.color==y.color);
Packit df99a1
}
Packit df99a1
Packit df99a1
Packit df99a1
// -- Helper for merge_and_split_ccs
Packit df99a1
static int
Packit df99a1
makeccid(const Grid_x_Color &x, GMap<Grid_x_Color,int> &map, int &ncc)
Packit df99a1
{
Packit df99a1
  GPosition p = map.contains(x);
Packit df99a1
  if (p) return map[p];
Packit df99a1
  return map[x] = ncc++;
Packit df99a1
}
Packit df99a1
Packit df99a1
Packit df99a1
// -- Merges small ccs of similar color and splits large ccs
Packit df99a1
void
Packit df99a1
CCImage::merge_and_split_ccs(int smallsize, int largesize)
Packit df99a1
{
Packit df99a1
  int ncc = ccs.size();
Packit df99a1
  int nruns = runs.size();
Packit df99a1
  int splitsize = largesize;
Packit df99a1
  if (ncc <= 0) return;
Packit df99a1
  // Associative map for storing merged ccids
Packit df99a1
  GMap<Grid_x_Color,int> map;
Packit df99a1
  nregularccs = ncc;
Packit df99a1
  // Set the correct ccids for the runs
Packit df99a1
  for (int ccid=0; ccid
Packit df99a1
    {
Packit df99a1
      CC* cc = &ccs[ccid];
Packit df99a1
      if (cc->nrun <= 0) continue;
Packit df99a1
      Grid_x_Color key;
Packit df99a1
      key.color = cc->color;
Packit df99a1
      int ccheight = cc->bb.height();
Packit df99a1
      int ccwidth = cc->bb.width();
Packit df99a1
      if (ccheight<=smallsize && ccwidth<=smallsize)
Packit df99a1
        {
Packit df99a1
          key.gridi = (cc->bb.ymin+cc->bb.ymax)/splitsize/2;
Packit df99a1
          key.gridj = (cc->bb.xmin+cc->bb.xmax)/splitsize/2;
Packit df99a1
          int newccid = makeccid(key, map, ncc);
Packit df99a1
          for(int runid=cc->frun; runid<cc->frun+cc->nrun; runid++)
Packit df99a1
            runs[runid].ccid = newccid;
Packit df99a1
        }
Packit df99a1
      else if (ccheight>=largesize || ccwidth>=largesize)
Packit df99a1
        {
Packit df99a1
          for(int runid=cc->frun; runid<cc->frun+cc->nrun; runid++)
Packit df99a1
            {
Packit df99a1
              Run *r = & runs[runid];
Packit df99a1
              key.gridi = r->y/splitsize;
Packit df99a1
              key.gridj = r->x1/splitsize;
Packit df99a1
              int gridj_end = r->x2/splitsize;
Packit df99a1
              int gridj_span = gridj_end - key.gridj;
Packit df99a1
              r->ccid = makeccid(key, map, ncc);
Packit df99a1
              if (gridj_span>0)
Packit df99a1
                {
Packit df99a1
                  // truncate current run 
Packit df99a1
                  runs.touch(nruns+gridj_span-1);
Packit df99a1
                  r = &runs[runid];
Packit df99a1
                  int x = key.gridj*splitsize + splitsize;
Packit df99a1
                  int x_end = r->x2;
Packit df99a1
                  r->x2 = x-1;
Packit df99a1
                  // append additional runs to the runs array
Packit df99a1
                  while (++key.gridj < gridj_end)
Packit df99a1
                    {
Packit df99a1
                      Run& newrun = runs[nruns++];
Packit df99a1
                      newrun.y = r->y;
Packit df99a1
                      newrun.x1 = x;
Packit df99a1
                      x += splitsize;
Packit df99a1
                      newrun.x2 = x-1;
Packit df99a1
                      newrun.color = key.color;
Packit df99a1
                      newrun.ccid = makeccid(key, map, ncc);
Packit df99a1
                    }
Packit df99a1
                  // append last run to the run array
Packit df99a1
                  Run& newrun = runs[nruns++];
Packit df99a1
                  newrun.y = r->y;
Packit df99a1
                  newrun.x1 = x;
Packit df99a1
                  newrun.x2 = x_end;
Packit df99a1
                  newrun.color = key.color;
Packit df99a1
                  newrun.ccid = makeccid(key, map, ncc);
Packit df99a1
                }
Packit df99a1
            }
Packit df99a1
        }
Packit df99a1
    }
Packit df99a1
  // Recompute cc descriptors
Packit df99a1
  make_ccs_from_ccids();
Packit df99a1
}
Packit df99a1
Packit df99a1
Packit df99a1
// -- Helps sorting cc
Packit df99a1
static int 
Packit df99a1
top_edges_descending (const void *pa, const void *pb)
Packit df99a1
{
Packit df99a1
  if (((CC*) pa)->bb.ymax != ((CC*) pb)->bb.ymax)
Packit df99a1
    return (((CC*) pb)->bb.ymax - ((CC*) pa)->bb.ymax);
Packit df99a1
  if (((CC*) pa)->bb.xmin != ((CC*) pb)->bb.xmin)
Packit df99a1
    return (((CC*) pa)->bb.xmin - ((CC*) pb)->bb.xmin);
Packit df99a1
  return (((CC*) pa)->frun - ((CC*) pb)->frun);
Packit df99a1
}
Packit df99a1
Packit df99a1
Packit df99a1
// -- Helps sorting cc
Packit df99a1
static int 
Packit df99a1
left_edges_ascending (const void *pa, const void *pb)
Packit df99a1
{
Packit df99a1
  if (((CC*) pa)->bb.xmin != ((CC*) pb)->bb.xmin)
Packit df99a1
    return (((CC*) pa)->bb.xmin - ((CC*) pb)->bb.xmin);
Packit df99a1
  if (((CC*) pb)->bb.ymax != ((CC*) pa)->bb.ymax)
Packit df99a1
    return (((CC*) pb)->bb.ymax - ((CC*) pa)->bb.ymax);
Packit df99a1
  return (((CC*) pa)->frun - ((CC*) pb)->frun);
Packit df99a1
}
Packit df99a1
Packit df99a1
Packit df99a1
// -- Helps sorting cc
Packit df99a1
static int 
Packit df99a1
integer_ascending (const void *pa, const void *pb)
Packit df99a1
{
Packit df99a1
  return ( *(int*)pb - *(int*)pa );
Packit df99a1
}
Packit df99a1
Packit df99a1
Packit df99a1
// -- Sort ccs in approximate reading order
Packit df99a1
void 
Packit df99a1
CCImage::sort_in_reading_order()
Packit df99a1
{
Packit df99a1
  if (nregularccs<2) return;
Packit df99a1
  CC *ccarray = new CC[nregularccs];
Packit df99a1
  // Copy existing ccarray (but segregate special ccs)
Packit df99a1
  int ccid;
Packit df99a1
  for(ccid=0; ccid
Packit df99a1
    ccarray[ccid] = ccs[ccid];
Packit df99a1
  // Sort the ccarray list into top-to-bottom order.
Packit df99a1
  qsort (ccarray, nregularccs, sizeof(CC), top_edges_descending);
Packit df99a1
  // Subdivide the ccarray list roughly into text lines
Packit df99a1
  int maxtopchange = width / 40;
Packit df99a1
  if (maxtopchange < 32) 
Packit df99a1
    maxtopchange = 32;
Packit df99a1
  // - Loop until processing all ccs
Packit df99a1
  int ccno = 0;
Packit df99a1
  int *bottoms = new int[nregularccs];
Packit df99a1
  while (ccno < nregularccs)
Packit df99a1
    {
Packit df99a1
      // - Gather first line approximation
Packit df99a1
      int nccno;
Packit df99a1
      int sublist_top = ccarray[ccno].bb.ymax-1;
Packit df99a1
      int sublist_bottom = ccarray[ccno].bb.ymin;
Packit df99a1
      for (nccno=ccno; nccno < nregularccs; nccno++)
Packit df99a1
        {
Packit df99a1
          if (ccarray[nccno].bb.ymax-1 < sublist_bottom) break;
Packit df99a1
          if (ccarray[nccno].bb.ymax-1 < sublist_top - maxtopchange) break;
Packit df99a1
          int bottom = ccarray[nccno].bb.ymin;
Packit df99a1
          bottoms[nccno-ccno] = bottom;
Packit df99a1
          if (bottom < sublist_bottom)
Packit df99a1
            sublist_bottom = bottom;
Packit df99a1
        }
Packit df99a1
      // - If more than one candidate cc for the line
Packit df99a1
      if (nccno > ccno + 1)
Packit df99a1
        {
Packit df99a1
          // - Compute median bottom
Packit df99a1
          qsort(bottoms, nccno-ccno, sizeof(int), integer_ascending);
Packit df99a1
          int bottom = bottoms[ (nccno-ccno-1)/2 ];
Packit df99a1
          // - Compose final line
Packit df99a1
          for (nccno=ccno; nccno < nregularccs; nccno++)
Packit df99a1
            if (ccarray[nccno].bb.ymax-1 < bottom)
Packit df99a1
              break;
Packit df99a1
          // - Sort final line
Packit df99a1
          qsort (ccarray+ccno, nccno-ccno, sizeof(CC), left_edges_ascending);
Packit df99a1
        }
Packit df99a1
      // - Next line
Packit df99a1
      ccno = nccno;
Packit df99a1
    }
Packit df99a1
  // Copy ccarray back and renumber the runs
Packit df99a1
  for(ccid=0; ccid
Packit df99a1
    {
Packit df99a1
      CC& cc = ccarray[ccid];
Packit df99a1
      ccs[ccid] = cc;
Packit df99a1
      for(int r=cc.frun; r
Packit df99a1
        runs[r].ccid = ccid;
Packit df99a1
    }
Packit df99a1
  // Free memory
Packit df99a1
  delete [] bottoms;
Packit df99a1
  delete[] ccarray;
Packit df99a1
}
Packit df99a1
Packit df99a1
Packit df99a1
// -- Creates a bitmap for a particular component
Packit df99a1
GP<GBitmap>   
Packit df99a1
CCImage::get_bitmap_for_cc(const int ccid) const
Packit df99a1
{
Packit df99a1
  const CC &cc = ccs[ccid];
Packit df99a1
  const GRect &bb = cc.bb;
Packit df99a1
  GP<GBitmap> bits = GBitmap::create(bb.height(), bb.width());
Packit df99a1
  const Run *prun = & runs[(int)cc.frun];
Packit df99a1
  for (int i=0; i
Packit df99a1
    {
Packit df99a1
      if (prun->y<bb.ymin || prun->y>=bb.ymax)
Packit df99a1
        G_THROW("Internal error (y bounds)");
Packit df99a1
      if (prun->x1<bb.xmin || prun->x2>=bb.xmax)
Packit df99a1
        G_THROW("Internal error (x bounds)");
Packit df99a1
      unsigned char *row = (*bits)[prun->y - bb.ymin];
Packit df99a1
      for (int x=prun->x1; x<=prun->x2; x++)
Packit df99a1
        row[x - bb.xmin] = 1;
Packit df99a1
    }
Packit df99a1
  return bits;
Packit df99a1
}
Packit df99a1
Packit df99a1
Packit df99a1
// -- Marks cc for deletion
Packit df99a1
void 
Packit df99a1
CCImage::erase_cc(int ccid)
Packit df99a1
{
Packit df99a1
  CC &cc = ccs[ccid];
Packit df99a1
  Run *r = &runs[cc.frun];
Packit df99a1
  int nr = cc.nrun;
Packit df99a1
  cc.nrun = 0;
Packit df99a1
  cc.npix = 0;
Packit df99a1
  while (--nr >= 0)
Packit df99a1
    (r++)->ccid = -1;  // will be deleted by make_ccs_from_ccids()
Packit df99a1
}
Packit df99a1
Packit df99a1
Packit df99a1
Packit df99a1
Packit df99a1
// --------------------------------------------------
Packit df99a1
// DEMOTION OF FOREGROUND CCS TO BACKGROUND STATUS
Packit df99a1
// --------------------------------------------------
Packit df99a1
Packit df99a1
Packit df99a1
// ISSUE: DEMOTION OF CCS (UNIMPLEMENTED) 
Packit df99a1
Packit df99a1
// The current code uses a single color for the background layer.  Many large,
Packit df99a1
// non matching, ccs however may be better encoded as part of the background
Packit df99a1
// layer.  A way to achieve this could be to consider each cc and evaluate the
Packit df99a1
// costs of coding it as foreground (does it match other ccs, does it comes
Packit df99a1
// with a complex geometry) or background (does its color blend smoothly with
Packit df99a1
// the surrounding parts of the background image).  One just needs then to
Packit df99a1
// remove the demoted ccs from the mask using CCImage::erase_cc() and
Packit df99a1
// recompute the ccs using CCImage::make_ccs_from_ccids().  Defining the
Packit df99a1
// compilation symbols BACKGROUND_SUBSAMPLING_FACTOR and
Packit df99a1
// PROGRESSIVE_BACKGROUND will enable code for computing the background from
Packit df99a1
// the input image and the provided mask.
Packit df99a1
Packit df99a1
Packit df99a1
// --------------------------------------------------
Packit df99a1
// MAIN COMPRESSION ROUTINE
Packit df99a1
// --------------------------------------------------
Packit df99a1
Packit df99a1
Packit df99a1
// -- Options for low color compression
Packit df99a1
struct cpaldjvuopts
Packit df99a1
{
Packit df99a1
  int ncolors;
Packit df99a1
  int dpi;
Packit df99a1
  bool verbose;
Packit df99a1
  bool bgwhite;
Packit df99a1
};
Packit df99a1
Packit df99a1
Packit df99a1
// -- Compresses low color pixmap.
Packit df99a1
void 
Packit df99a1
cpaldjvu(ByteStream *ibs, GURL &urlout, const cpaldjvuopts &opts)
Packit df99a1
{
Packit df99a1
  GP<GPixmap> ginput=GPixmap::create(*ibs);
Packit df99a1
  int w = ginput->columns();
Packit df99a1
  int h = ginput->rows();
Packit df99a1
  int dpi = MAX(200, MIN(900, opts.dpi));
Packit df99a1
  int largesize = MIN(500, MAX(64, dpi));
Packit df99a1
  int smallsize = MAX(2, dpi/150);
Packit df99a1
Packit df99a1
  // Compute optimal palette and quantize input pixmap
Packit df99a1
  GP<DjVuPalette> gpal=DjVuPalette::create();
Packit df99a1
  DjVuPalette &pal=*gpal;
Packit df99a1
  GPixel bgcolor;
Packit df99a1
  int bgindex = -1;
Packit df99a1
  if (! opts.bgwhite)
Packit df99a1
    {
Packit df99a1
      bgindex = pal.compute_pixmap_palette(*ginput, opts.ncolors);
Packit df99a1
      pal.index_to_color(bgindex, bgcolor);
Packit df99a1
    }
Packit df99a1
  else
Packit df99a1
    {
Packit df99a1
      bgcolor = GPixel::WHITE;
Packit df99a1
      pal.histogram_clear();
Packit df99a1
      for (int j=0; j
Packit df99a1
        {
Packit df99a1
          const GPixel *p = (*ginput)[j];
Packit df99a1
          for (int i=0; i
Packit df99a1
            if (p[i] != GPixel::WHITE)
Packit df99a1
              pal.histogram_add(p[i], 1);
Packit df99a1
        }
Packit df99a1
      pal.compute_palette(opts.ncolors);
Packit df99a1
    }
Packit df99a1
  if (opts.verbose)
Packit df99a1
    DjVuFormatErrorUTF8( "%s\t%d\t%d\t%d",
Packit df99a1
                         ERR_MSG("cpaldjvu.quantizied"), 
Packit df99a1
                         w, h, pal.size());
Packit df99a1
  if (opts.verbose && !opts.bgwhite)
Packit df99a1
    DjVuPrintErrorUTF8("cpaldjvu: background color is #%02x%02x%02x.\n", 
Packit df99a1
                       bgcolor.r, bgcolor.g, bgcolor.b);
Packit df99a1
  
Packit df99a1
  // Fill CCImage with color runs
Packit df99a1
  int xruncount=0,yruncount=0;
Packit df99a1
  CCImage rimg(w, h);
Packit df99a1
  int *line;
Packit df99a1
  GPBuffer<int> gline(line,w);
Packit df99a1
  int *prevline;
Packit df99a1
  GPBuffer<int> gprevline(prevline,w);
Packit df99a1
  for (int x=0;x
Packit df99a1
  {
Packit df99a1
    prevline[x]=bgindex;
Packit df99a1
  }
Packit df99a1
  for (int y=0; y
Packit df99a1
    {
Packit df99a1
      int x;
Packit df99a1
      const GPixel *row = (*ginput)[y];
Packit df99a1
      for(x=0;x
Packit df99a1
        {
Packit df99a1
          line[x] = pal.color_to_index(row[x]);
Packit df99a1
          if (opts.bgwhite && row[x]==GPixel::WHITE)
Packit df99a1
            line[x] = bgindex;
Packit df99a1
        }
Packit df99a1
      for(x=0;x
Packit df99a1
        {
Packit df99a1
          int x1 = x;
Packit df99a1
          int index = line[x++];
Packit df99a1
          while (x
Packit df99a1
          if (index != bgindex)
Packit df99a1
            {
Packit df99a1
              xruncount++;
Packit df99a1
              rimg.add_single_run(y, x1, x-1, index);
Packit df99a1
            }
Packit df99a1
        }
Packit df99a1
      for(x=0;x
Packit df99a1
        if(prevline[x] != line[x]) yruncount++;
Packit df99a1
      gprevline.swap(gline);
Packit df99a1
    }
Packit df99a1
  ginput = 0; //save memory
Packit df99a1
  if (opts.verbose)
Packit df99a1
    DjVuFormatErrorUTF8( "%s\t%d", ERR_MSG("cpaldjvu.color_runs"), 
Packit df99a1
                         rimg.runs.size());
Packit df99a1
Packit df99a1
  // Perform Color Connected Component Analysis
Packit df99a1
  rimg.make_ccids_by_analysis();                  // Obtain ccids
Packit df99a1
  rimg.make_ccs_from_ccids();                     // Compute cc descriptors
Packit df99a1
  if (opts.verbose)
Packit df99a1
    DjVuFormatErrorUTF8( "%s\t%d", ERR_MSG("cpaldjvu.ccs_before"), 
Packit df99a1
                         rimg.ccs.size());
Packit df99a1
  rimg.merge_and_split_ccs(smallsize,largesize);  // Eliminates gross ccs
Packit df99a1
  if (opts.verbose)
Packit df99a1
    DjVuFormatErrorUTF8( "%s\t%d", ERR_MSG("cpaldjvu.ccs_after"), 
Packit df99a1
                         rimg.ccs.size());
Packit df99a1
  rimg.sort_in_reading_order();                   // Sort cc descriptors
Packit df99a1
  
Packit df99a1
  // Create JB2Image and fill colordata
Packit df99a1
  GP<JB2Image> gjimg=JB2Image::create();
Packit df99a1
  JB2Image &jimg=*gjimg;
Packit df99a1
  jimg.set_dimension(w, h);
Packit df99a1
  int nccs = rimg.ccs.size();
Packit df99a1
  for (int ccid=0; ccid
Packit df99a1
    {
Packit df99a1
      JB2Shape shape;
Packit df99a1
      JB2Blit  blit;
Packit df99a1
      shape.parent = -1;
Packit df99a1
      shape.userdata = 0;
Packit df99a1
      if (ccid >= rimg.nregularccs)
Packit df99a1
        shape.userdata |= JB2SHAPE_SPECIAL;
Packit df99a1
      shape.bits = rimg.get_bitmap_for_cc(ccid);
Packit df99a1
      shape.bits->compress();
Packit df99a1
      CC& cc = rimg.ccs[ccid];
Packit df99a1
      blit.shapeno = jimg.add_shape(shape);
Packit df99a1
      blit.left = cc.bb.xmin;
Packit df99a1
      blit.bottom = cc.bb.ymin;
Packit df99a1
      int blitno = jimg.add_blit(blit);
Packit df99a1
      pal.colordata.touch(blitno);
Packit df99a1
      pal.colordata[blitno] = cc.color;
Packit df99a1
    }
Packit df99a1
  
Packit df99a1
  // Organize JB2Image
Packit df99a1
  tune_jb2image_lossless(&jimg);
Packit df99a1
  if (opts.verbose)
Packit df99a1
    {
Packit df99a1
      int nshape=0, nrefine=0;
Packit df99a1
      for (int i=0; i
Packit df99a1
        if (!jimg.get_shape(i).bits) continue;
Packit df99a1
        if (jimg.get_shape(i).parent >= 0) nrefine++; 
Packit df99a1
        nshape++; 
Packit df99a1
      }
Packit df99a1
      DjVuFormatErrorUTF8( "%s\t%d\t%d",
Packit df99a1
                       ERR_MSG("cpaldjvu.cross_code"), 
Packit df99a1
                       nshape, nrefine);
Packit df99a1
    }
Packit df99a1
  
Packit df99a1
  // Create background image
Packit df99a1
#ifdef BACKGROUND_SUBSAMPLING_FACTOR
Packit df99a1
  // -- we may create the background by masking and subsampling
Packit df99a1
  GP<GPixmap> ginputsub=GPixmap::create();
Packit df99a1
  GPixmap &inputsub=*ginputsub;
Packit df99a1
  GP<GBitmap> mask = jimg.get_bitmap(BACKGROUND_SUBSAMPLING_FACTOR);
Packit df99a1
  inputsub.downsample(&input, BACKGROUND_SUBSAMPLING_FACTOR);
Packit df99a1
  GP<IW44Image> iwimage=IW44Image::create(inputsub, mask);
Packit df99a1
#else
Packit df99a1
  // -- but who cares since the background is uniform.
Packit df99a1
  GP<GPixmap> ginputsub=GPixmap::create((h+11)/12, (w+11)/12, &bgcolor);
Packit df99a1
  GPixmap &inputsub=*ginputsub;
Packit df99a1
  GP<IW44Image> iwimage=IW44Image::create_encode(inputsub);
Packit df99a1
#endif
Packit df99a1
Packit df99a1
  // Assemble DJVU file
Packit df99a1
  GP<ByteStream> obs=ByteStream::create(urlout, "wb");
Packit df99a1
  GP<IFFByteStream> giff=IFFByteStream::create(obs);
Packit df99a1
  IFFByteStream &iff=*giff;
Packit df99a1
  // -- main composite chunk
Packit df99a1
  iff.put_chunk("FORM:DJVU", 1);
Packit df99a1
  // -- ``INFO'' chunk
Packit df99a1
  iff.put_chunk("INFO");
Packit df99a1
  GP<DjVuInfo> ginfo=DjVuInfo::create();
Packit df99a1
  DjVuInfo info=*ginfo;
Packit df99a1
  info.height = h;
Packit df99a1
  info.width = w;
Packit df99a1
  info.dpi = opts.dpi;
Packit df99a1
  info.encode(*iff.get_bytestream());
Packit df99a1
  iff.close_chunk();
Packit df99a1
  // -- ``Sjbz'' chunk
Packit df99a1
  iff.put_chunk("Sjbz");
Packit df99a1
  jimg.encode(iff.get_bytestream());
Packit df99a1
  iff.close_chunk();
Packit df99a1
  // -- ``FGbz'' chunk
Packit df99a1
  iff.put_chunk("FGbz");
Packit df99a1
  pal.encode(iff.get_bytestream());
Packit df99a1
  iff.close_chunk();
Packit df99a1
  // -- ``BG44'' chunk
Packit df99a1
  IWEncoderParms iwparms;
Packit df99a1
#ifdef PROGRESSIVE_BACKGROUND
Packit df99a1
  // ----- we may use several chunks to enable progressive rendering ...
Packit df99a1
  iff.put_chunk("BG44");
Packit df99a1
  iwparms.slices = 74;
Packit df99a1
  iwimage->encode_chunk(iff, iwparms);
Packit df99a1
  iff.close_chunk();
Packit df99a1
  iff.put_chunk("BG44");
Packit df99a1
  iwparms.slices = 87;
Packit df99a1
  iwimage->encode_chunk(iff, iwparms);
Packit df99a1
  iff.close_chunk();
Packit df99a1
#endif
Packit df99a1
  // ----- but who cares when the background is so small.
Packit df99a1
  iff.put_chunk("BG44");
Packit df99a1
  iwparms.slices = 97;
Packit df99a1
  iwimage->encode_chunk(iff.get_bytestream(), iwparms);
Packit df99a1
  iff.close_chunk();
Packit df99a1
  // -- terminate main composite chunk
Packit df99a1
  iff.close_chunk();
Packit df99a1
  // Finished!
Packit df99a1
}  
Packit df99a1
Packit df99a1
Packit df99a1
Packit df99a1
// --------------------------------------------------
Packit df99a1
// MAIN
Packit df99a1
// --------------------------------------------------
Packit df99a1
Packit df99a1
Packit df99a1
void
Packit df99a1
usage()
Packit df99a1
{
Packit df99a1
  DjVuPrintErrorUTF8(
Packit df99a1
#ifdef DJVULIBRE_VERSION
Packit df99a1
         "CPALDJVU --- DjVuLibre-" DJVULIBRE_VERSION "\n"
Packit df99a1
#endif
Packit df99a1
         "DjVu encoder for images with few colors\n\n"
Packit df99a1
         "Usage: cpaldjvu [options] <inputppmfile> <outputdjvufile>\n"
Packit df99a1
         "Options are:\n"
Packit df99a1
         "   -colors [2-4096] Maximum number of colors during quantization (default 256).\n"
Packit df99a1
         "   -dpi [25-6000]   Resolution written into the output file (default 100).\n"
Packit df99a1
         "   -verbose         Displays additional messages.\n"
Packit df99a1
         "   -bgwhite         Use the lightest color for background (usually white).\n"
Packit df99a1
         );
Packit df99a1
  exit(10);
Packit df99a1
}
Packit df99a1
Packit df99a1
Packit df99a1
int 
Packit df99a1
main(int argc, const char **argv)
Packit df99a1
{
Packit df99a1
  DJVU_LOCALE;
Packit df99a1
  GArray<GUTF8String> dargv(0,argc-1);
Packit df99a1
  for(int i=0;i
Packit df99a1
    dargv[i]=GNativeString(argv[i]);
Packit df99a1
  G_TRY
Packit df99a1
    {
Packit df99a1
      GURL inputppmurl;
Packit df99a1
      GURL outputdjvuurl;
Packit df99a1
      // Defaults
Packit df99a1
      cpaldjvuopts opts;
Packit df99a1
      opts.dpi = 100;
Packit df99a1
      opts.ncolors = 256;
Packit df99a1
      opts.verbose = false;
Packit df99a1
      opts.bgwhite = false;
Packit df99a1
      // Parse options
Packit df99a1
      for (int i=1; i
Packit df99a1
        {
Packit df99a1
          GUTF8String arg = dargv[i];
Packit df99a1
          if (arg == "-colors" && i+1
Packit df99a1
            {
Packit df99a1
              char *end;
Packit df99a1
              opts.ncolors = strtol(dargv[++i], &end, 10);
Packit df99a1
              if (*end || opts.ncolors<2 || opts.ncolors>4096)
Packit df99a1
                usage();
Packit df99a1
            }
Packit df99a1
          else if (arg == "-dpi" && i+1
Packit df99a1
            {
Packit df99a1
              char *end;
Packit df99a1
              opts.dpi = strtol(dargv[++i], &end, 10);
Packit df99a1
              if (*end || opts.dpi<25 || opts.dpi>6000)
Packit df99a1
                usage();
Packit df99a1
            }
Packit df99a1
          else if (arg == "-verbose" || arg == "-v")
Packit df99a1
            opts.verbose = true;
Packit df99a1
          else if (arg == "-bgwhite")
Packit df99a1
            opts.bgwhite = true;
Packit df99a1
          else if (arg[0] == '-' && arg[1])
Packit df99a1
            usage();
Packit df99a1
          else if (inputppmurl.is_empty())
Packit df99a1
            inputppmurl = GURL::Filename::UTF8(arg);
Packit df99a1
          else if (outputdjvuurl.is_empty())
Packit df99a1
            outputdjvuurl = GURL::Filename::UTF8(arg);
Packit df99a1
          else
Packit df99a1
            usage();
Packit df99a1
        }
Packit df99a1
      if (inputppmurl.is_empty() || outputdjvuurl.is_empty())
Packit df99a1
        usage();
Packit df99a1
      // Load and run
Packit df99a1
      GP<ByteStream> ibs=ByteStream::create(inputppmurl,"rb");
Packit df99a1
      cpaldjvu(ibs, outputdjvuurl, opts);
Packit df99a1
    }
Packit df99a1
  G_CATCH(ex)
Packit df99a1
    {
Packit df99a1
      ex.perror();
Packit df99a1
      exit(1);
Packit df99a1
    }
Packit df99a1
  G_ENDCATCH;
Packit df99a1
  return 0;
Packit df99a1
}
Packit df99a1