|
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 |
|