//C- -*- C++ -*-
//C- -------------------------------------------------------------------
//C- DjVuLibre-3.5
//C- Copyright (c) 2002 Leon Bottou and Yann Le Cun.
//C- Copyright (c) 2001 AT&T
//C-
//C- This software is subject to, and may be distributed under, the
//C- GNU General Public License, either Version 2 of the license,
//C- or (at your option) any later version. The license should have
//C- accompanied the software or you may obtain a copy of the license
//C- from the Free Software Foundation at http://www.fsf.org .
//C-
//C- This program is distributed in the hope that it will be useful,
//C- but WITHOUT ANY WARRANTY; without even the implied warranty of
//C- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//C- GNU General Public License for more details.
//C-
//C- DjVuLibre-3.5 is derived from the DjVu(r) Reference Library from
//C- Lizardtech Software. Lizardtech Software has authorized us to
//C- replace the original DjVu(r) Reference Library notice by the following
//C- text (see doc/lizard2002.djvu and doc/lizardtech2007.djvu):
//C-
//C- ------------------------------------------------------------------
//C- | DjVu (r) Reference Library (v. 3.5)
//C- | Copyright (c) 1999-2001 LizardTech, Inc. All Rights Reserved.
//C- | The DjVu Reference Library is protected by U.S. Pat. No.
//C- | 6,058,214 and patents pending.
//C- |
//C- | This software is subject to, and may be distributed under, the
//C- | GNU General Public License, either Version 2 of the license,
//C- | or (at your option) any later version. The license should have
//C- | accompanied the software or you may obtain a copy of the license
//C- | from the Free Software Foundation at http://www.fsf.org .
//C- |
//C- | The computer code originally released by LizardTech under this
//C- | license and unmodified by other parties is deemed "the LIZARDTECH
//C- | ORIGINAL CODE." Subject to any third party intellectual property
//C- | claims, LizardTech grants recipient a worldwide, royalty-free,
//C- | non-exclusive license to make, use, sell, or otherwise dispose of
//C- | the LIZARDTECH ORIGINAL CODE or of programs derived from the
//C- | LIZARDTECH ORIGINAL CODE in compliance with the terms of the GNU
//C- | General Public License. This grant only confers the right to
//C- | infringe patent claims underlying the LIZARDTECH ORIGINAL CODE to
//C- | the extent such infringement is reasonably necessary to enable
//C- | recipient to make, have made, practice, sell, or otherwise dispose
//C- | of the LIZARDTECH ORIGINAL CODE (or portions thereof) and not to
//C- | any greater extent that may be necessary to utilize further
//C- | modifications or combinations.
//C- |
//C- | The LIZARDTECH ORIGINAL CODE is provided "AS IS" WITHOUT WARRANTY
//C- | OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
//C- | TO ANY WARRANTY OF NON-INFRINGEMENT, OR ANY IMPLIED WARRANTY OF
//C- | MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
//C- +------------------------------------------------------------------
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#if NEED_GNUG_PRAGMAS
# pragma implementation
#endif
/** @name c44
{\bf Synopsis}\\
\begin{verbatim}
c44 [options] pnmfile [djvufile]
c44 [options] jpegfile [djvufile]
\end{verbatim}
{\bf Description} ---
File #"c44.cpp"# illustrates the use of classes \Ref{IWBitmap} and
\Ref{IWPixmap} for compressing and encoding a color image or a gray level
image using the DjVu IW44 wavelets. This is the preferred mode for
creating a DjVu image which does not require separate layers for encoding
the text and the background images. The files created by #c44# are
legal Photo DjVu files recognized by the DjVu decoder (see program \Ref{ddjvu}).
{\bf Arguments} ---
Argument #pnmfile# or #jpegfile# is the name of the input file. PGM files
are recognized for gray level images; PPM files are recognized for color
images. Popular file formats can be converted to PGM or PPM using the
NetPBM package (\URL{http://www.arc.umn.edu/GVL/Software/netpbm.html})
or the ImageMagick package (\URL{http://www.wizards.dupont.com/cristy/}).
Optional argument #djvufile# is the name of the output file. It is
customary to use either suffix #".djvu"#, #".djv"#, #".iw44"# or #".iw4"#.
Suffix #".djvu"# emphasizes the fact that IW44 files is seamlessly
recognized by the current DjVu decoder. Suffix #".iw4"# however was
required by older versions of the DjVu plugin. If this argument is
omitted, a filename is generated by replacing the suffix of #pnmfile#
or #jpegfile# with suffix #".djvu"#.
{\bf Quality Specification}---
Files produced by the DjVu IW44 Wavelet Encoder are IFF files composed of
an arbitrary number of chunks (see \Ref{showiff} and \Ref{IWImage.h})
containing successive refinements of the encoded image. Each chunk is
composed of several slices. A typical file contains a total of 100 slices
split between three or four chunks. Various options provide quality
targets (#-decibel#), slicing targets (#-slice#) or file size targets
(#-bpp# or #-size#) for each of these refinements. Chunks are generated
until meeting either the decibel target, the file size target, or the
slicing target for each chunk.
\begin{description}
\item[-bpp n,..,n]
Selects a increasing sequence of bitrates for building
progressive IW44 file (in bits per pixel).
\item[-size n,..,n]
Selects a increasing sequence of minimal sizes for building
progressive IW44 file (in bytes).
\item[-decibel n,..,n]
Selects an increasing sequence of luminance error expressed as decibels
ranging from 16 (very low quality) to 48 (very high quality). This
criterion should not be used when recoding an image which was already
compressed with a lossy compression scheme (such as Wavelets or JPEG)
because successive losses of quality accumulate.
\item[-slice n+...+n]
Selects an increasing sequence of data slices expressed as integers
ranging from 1 to 140.
\end{description}
These options take a target specification list expressed either as a comma
separated list of increasing numbers or as a list of numbers separated by
character #'+'#. Both commands below for instance are equivalent:
\begin{verbatim}
c44 -bpp 0.1,0.2,0.5 inputfile.ppm outputfile.djvu
c44 -bpp 0.1+0.1+0.3 inputfile.ppm outputfile.djvu
\end{verbatim}
Both these commands generate a file whose first chunk encodes the image
with 0.1 bits per pixel, whose second chunk refines the image to 0.2 bits
per pixel and whose third chunk refines the image to 0.5 bits per pixel.
In other words, the second chunk provides an extra 0.1 bits per pixel and
the third chunk provides an extra 0.3 bits per pixels.
When no quality specification is provided, program #c44# usually generates
a file composed of three progressive refinement chunks whose quality
should be acceptable. The best results however are achieved by precisely
tuning the image quality. As a rule of thumb, #c44# generates an
acceptable quality when you specify a size equal to 50% to 75% of the size
of a comparable JPEG image.
{\bf Color Processing Specification} ---
Five options control the encoding of the chrominance information of color
images. These options are of course meaningless for processing a gray
level image.
\begin{description}
\item[-crcbnormal]
Selects normal chrominance encoding (default). Chrominance information is
encoded at the same resolution as the luminance.
\item[-crcbhalf]
Selects half resolution chrominance encoding. Chrominance information is
encoded at half the luminance resolution.
\item[-crcbdelay n]
This option can be used with #-crcbnormal# and #-crcbhalf# for specifying
an encoding delay which reduces the bitrate associated with the chrominance. The
default chrominance encoding delay is 10 slices.
\item[-crcbfull]
Selects the highest possible quality for encoding the chrominance information. This
is equivalent to specifying #-crcbnormal# and #-crcbdelay 0#.
\item[-crcbnone]
Disables encoding of the chrominance. Only the luminance information will
be encoded. The resulting image will show in shades of gray.
\end{description}
{\bf Advanced Options} ---
Program #c44# also recognizes the following options:
\begin{description}
\item[-dbfrac f]
This option alters the meaning of the -decibel option. The decibel target then
addresses only the average error of the specified fraction of the most
misrepresented 32x32 pixel blocks.
\item[-mask pbmfile]
This option can be used when we know that certain pixels of a background
image are going to be covered by foreground objects like text or drawings.
File #pbmfile# must be a PBM file whose size matches the size of the input
file. Each black pixel in #pbmfile# means that the value of the corresponding
pixel in the input file is irrelevant. The DjVu IW44 Encoder will replace
the masked pixels by a color value whose coding cost is minimal (see
\URL{http://www.research.att.com/~leonb/DJVU/mask}).
\end{description}
{\bf Photo DjVu options} ---
Photo DjVu images have the additional capability to store the resolution
and gamma correction information.
\begin{description}
\item[-dpi n] Sets the resolution information for a Photo DjVu image.
\item[-gamma n] Sets the gamma correction information for a Photo DjVu image.
\end{description}
{\bf Performance} ---
The main design objective for the DjVu wavelets consisted of allowing
progressive rendering and smooth scrolling of large images with limited
memory requirements. Decoding functions process the compressed data and
update a memory efficient representation of the wavelet coefficients.
Imaging function then can quickly render an arbitrary segment of the image
using the available data. Both process can be carried out in two threads
of execution. This design plays an important role in the DjVu system.
We have investigated various state-of-the-art wavelet compression schemes:
although these schemes may achieve slightly smaller file sizes, the
decoding functions did not even approach our requirements. The IW44
wavelets reach these requirements today and may in the future implement
more modern refinements (such as trellis quantization, bitrate
allocation, etc.) if (and only if) these refinements can be implemented
within our constraints.
@memo
DjVu IW44 wavelet encoder.
@author
L\'eon Bottou <leonb@research.att.com>
*/
//@{
//@}
#include "GString.h"
#include "GException.h"
#include "IW44Image.h"
#include "DjVuInfo.h"
#include "IFFByteStream.h"
#include "GOS.h"
#include "GBitmap.h"
#include "GPixmap.h"
#include "GURL.h"
#include "DjVuMessage.h"
#include "JPEGDecoder.h"
#include "common.h"
// command line data
int flag_mask = 0;
int flag_bpp = 0;
int flag_size = 0;
int flag_percent = 0;
int flag_slice = 0;
int flag_decibel = 0;
int flag_crcbdelay = -1;
int flag_crcbmode = -1;
double flag_dbfrac = -1;
int flag_dpi = -1;
double flag_gamma = -1;
int argc_bpp = 0;
int argc_size = 0;
int argc_slice = 0;
int argc_decibel = 0;
IW44Image::CRCBMode arg_crcbmode = IW44Image::CRCBnormal;
#define MAXCHUNKS 64
float argv_bpp[MAXCHUNKS];
int argv_size[MAXCHUNKS];
int argv_slice[MAXCHUNKS];
float argv_decibel[MAXCHUNKS];
struct C44Global
{
// Globals that need static initialization
// are grouped here to work around broken compilers.
GURL pnmurl;
GURL iw4url;
GURL mskurl;
IWEncoderParms parms[MAXCHUNKS];
};
static C44Global& g(void)
{
static C44Global g;
return g;
}
// parse arguments
void
usage()
{
DjVuPrintErrorUTF8(
#ifdef DJVULIBRE_VERSION
"C44 --- DjVuLibre-" DJVULIBRE_VERSION "\n"
#endif
"Image compression utility using IW44 wavelets\n\n"
"Usage: c44 [options] pnm-or-jpeg-file [djvufile]\n"
"Options:\n"
" -slice n+...+n -- select an increasing sequence of data slices\n"
" expressed as integers ranging from 1 to 140.\n"
" -bpp n,..,n -- select a increasing sequence of bitrates\n"
" for building progressive file (in bits per pixel).\n"
" -size n,..,n -- select an increasing sequence of minimal sizes\n"
" for building progressive files (expressed in bytes).\n"
" -percent n,..,n -- selects the percentage of original file size\n"
" for building progressive file.\n"
" -decibel n,..,n -- select an increasing sequence of luminance error\n"
" expressed as decibels (ranging from 16 to 50).\n"
" -dbfrac frac -- restrict decibel estimation to a fraction of\n"
" the most misrepresented 32x32 blocks\n"
" -mask pbmfile -- select bitmask specifying image zone to encode\n"
" with minimal bitrate. (default none)\n"
" -dpi n -- sets the image resolution\n"
" -gamma n -- sets the image gamma correction\n"
" -crcbfull -- encode chrominance with highest quality\n"
" -crcbnormal -- encode chrominance with normal resolution (default)\n"
" -crcbhalf -- encode chrominance with half resolution\n"
" -crcbnone -- do not encode chrominance at all\n"
" -crcbdelay n -- select chrominance coding delay (default 10)\n"
" for -crcbnormal and -crcbhalf modes\n"
"\n");
exit(1);
}
void
parse_bpp(const char *q)
{
flag_bpp = 1;
argc_bpp = 0;
double lastx = 0;
while (*q)
{
char *ptr;
double x = strtod(q, &ptr);
if (ptr == q)
G_THROW( ERR_MSG("c44.bitrate_not_number") );
if (lastx>0 && q[-1]=='+')
x += lastx;
if (x<=0 || x>24 || x<lastx)
G_THROW( ERR_MSG("c44.bitrate_out_of_range") );
lastx = x;
if (*ptr && *ptr!='+' && *ptr!=',')
G_THROW( ERR_MSG("c44.bitrate_comma_expected") );
q = (*ptr ? ptr+1 : ptr);
argv_bpp[argc_bpp++] = (float)x;
if (argc_bpp>MAXCHUNKS)
G_THROW( ERR_MSG("c44.bitrate_too_many") );
}
if (argc_bpp < 1)
G_THROW( ERR_MSG("c44.bitrate_no_chunks") );
}
void
parse_size(const char *q)
{
flag_size = 1;
argc_size = 0;
int lastx = 0;
while (*q)
{
char *ptr;
int x = strtol(q, &ptr, 10);
if (ptr == q)
G_THROW( ERR_MSG("c44.size_not_number") );
if (lastx>0 && q[-1]=='+')
x += lastx;
if (x<lastx)
G_THROW( ERR_MSG("c44.size_out_of_range") );
lastx = x;
if (*ptr && *ptr!='+' && *ptr!=',')
G_THROW( ERR_MSG("c44.size_comma_expected") );
q = (*ptr ? ptr+1 : ptr);
argv_size[argc_size++] = x;
if (argc_size>=MAXCHUNKS)
G_THROW( ERR_MSG("c44.size_too_many") );
}
if (argc_size < 1)
G_THROW( ERR_MSG("c44.size_no_chunks") );
}
void
parse_slice(const char *q)
{
flag_slice = 1;
argc_slice = 0;
int lastx = 0;
while (*q)
{
char *ptr;
int x = strtol(q, &ptr, 10);
if (ptr == q)
G_THROW( ERR_MSG("c44.slice_not_number") );
if (lastx>0 && q[-1]=='+')
x += lastx;
if (x<1 || x>1000 || x<lastx)
G_THROW( ERR_MSG("c44.slice_out_of_range") );
lastx = x;
if (*ptr && *ptr!='+' && *ptr!=',')
G_THROW( ERR_MSG("c44.slice_comma_expected") );
q = (*ptr ? ptr+1 : ptr);
argv_slice[argc_slice++] = x;
if (argc_slice>=MAXCHUNKS)
G_THROW( ERR_MSG("c44.slice_too_many") );
}
if (argc_slice < 1)
G_THROW( ERR_MSG("c44.slice_no_chunks") );
}
void
parse_decibel(const char *q)
{
flag_decibel = 1;
argc_decibel = 0;
double lastx = 0;
while (*q)
{
char *ptr;
double x = strtod(q, &ptr);
if (ptr == q)
G_THROW( ERR_MSG("c44.decibel_not_number") );
if (lastx>0 && q[-1]=='+')
x += lastx;
if (x<16 || x>50 || x<lastx)
G_THROW( ERR_MSG("c44.decibel_out_of_range") );
lastx = x;
if (*ptr && *ptr!='+' && *ptr!=',')
G_THROW( ERR_MSG("c44.decibel_comma_expected") );
q = (*ptr ? ptr+1 : ptr);
argv_decibel[argc_decibel++] = (float)x;
if (argc_decibel>=MAXCHUNKS)
G_THROW( ERR_MSG("c44.decibel_too_many") );
}
if (argc_decibel < 1)
G_THROW( ERR_MSG("c44.decibel_no_chunks") );
}
int
resolve_quality(int npix)
{
// Convert ratio specification into size specification
if (flag_bpp)
{
if (flag_size)
G_THROW( ERR_MSG("c44.exclusive") );
flag_size = flag_bpp;
argc_size = argc_bpp;
for (int i=0; i<argc_bpp; i++)
argv_size[i] = (int)(npix*argv_bpp[i]/8.0+0.5);
}
// Compute number of chunks
int nchunk = 0;
if (flag_slice && nchunk<argc_slice)
nchunk = argc_slice;
if (flag_size && nchunk<argc_size)
nchunk = argc_size;
if (flag_decibel && nchunk<argc_decibel)
nchunk = argc_decibel;
// Force default values
if (nchunk == 0)
{
#ifdef DECIBELS_25_30_34
nchunk = 3;
flag_decibel = 1;
argc_decibel = 3;
argv_decibel[0]=25;
argv_decibel[1]=30;
argv_decibel[2]=34;
#else
nchunk = 3;
flag_slice = 1;
argc_slice = 3;
argv_slice[0]=74;
argv_slice[1]=89;
argv_slice[2]=99;
#endif
}
// Complete short specifications
while (argc_size < nchunk)
argv_size[argc_size++] = 0;
while (argc_slice < nchunk)
argv_slice[argc_slice++] = 0;
while (argc_decibel < nchunk)
argv_decibel[argc_decibel++] = 0.0;
// Fill parm structure
for(int i=0; i<nchunk; i++)
{
g().parms[i].bytes = argv_size[i];
g().parms[i].slices = argv_slice[i];
g().parms[i].decibels = argv_decibel[i];
}
// Return number of chunks
return nchunk;
}
void
parse(GArray<GUTF8String> &argv)
{
const int argc=argv.hbound()+1;
for (int i=1; i<argc; i++)
{
if (argv[i][0] == '-')
{
if (argv[i] == "-percent")
{
if (++i >= argc)
G_THROW( ERR_MSG("c44.no_bpp_arg") );
if (flag_bpp || flag_size)
G_THROW( ERR_MSG("c44.multiple_bitrate") );
parse_size(argv[i]);
flag_percent = 1;
}
else if (argv[i] == "-bpp")
{
if (++i >= argc)
G_THROW( ERR_MSG("c44.no_bpp_arg") );
if (flag_bpp || flag_size)
G_THROW( ERR_MSG("c44.multiple_bitrate") );
parse_bpp(argv[i]);
}
else if (argv[i] == "-size")
{
if (++i >= argc)
G_THROW( ERR_MSG("c44.no_size_arg") );
if (flag_bpp || flag_size)
G_THROW( ERR_MSG("c44.multiple_size") );
parse_size(argv[i]);
}
else if (argv[i] == "-decibel")
{
if (++i >= argc)
G_THROW( ERR_MSG("c44.no_decibel_arg") );
if (flag_decibel)
G_THROW( ERR_MSG("c44.multiple_decibel") );
parse_decibel(argv[i]);
}
else if (argv[i] == "-slice")
{
if (++i >= argc)
G_THROW( ERR_MSG("c44.no_slice_arg") );
if (flag_slice)
G_THROW( ERR_MSG("c44.multiple_slice") );
parse_slice(argv[i]);
}
else if (argv[i] == "-mask")
{
if (++i >= argc)
G_THROW( ERR_MSG("c44.no_mask_arg") );
if (! g().mskurl.is_empty())
G_THROW( ERR_MSG("c44.multiple_mask") );
g().mskurl = GURL::Filename::UTF8(argv[i]);
}
else if (argv[i] == "-dbfrac")
{
if (++i >= argc)
G_THROW( ERR_MSG("c44.no_dbfrac_arg") );
if (flag_dbfrac>0)
G_THROW( ERR_MSG("c44.multiple_dbfrac") );
char *ptr;
flag_dbfrac = strtod(argv[i], &ptr);
if (flag_dbfrac<=0 || flag_dbfrac>1 || *ptr)
G_THROW( ERR_MSG("c44.illegal_dbfrac") );
}
else if (argv[i] == "-crcbnone")
{
if (flag_crcbmode>=0 || flag_crcbdelay>=0)
G_THROW( ERR_MSG("c44.incompatable_chrominance") );
flag_crcbdelay = flag_crcbmode = 0;
arg_crcbmode = IW44Image::CRCBnone;
}
else if (argv[i] == "-crcbhalf")
{
if (flag_crcbmode>=0)
G_THROW( ERR_MSG("c44.incompatable_chrominance") );
flag_crcbmode = 0;
arg_crcbmode = IW44Image::CRCBhalf;
}
else if (argv[i] == "-crcbnormal")
{
if (flag_crcbmode>=0)
G_THROW( ERR_MSG("c44.incompatable_chrominance") );
flag_crcbmode = 0;
arg_crcbmode = IW44Image::CRCBnormal;
}
else if (argv[i] == "-crcbfull")
{
if (flag_crcbmode>=0 || flag_crcbdelay>=0)
G_THROW( ERR_MSG("c44.incompatable_chrominance") );
flag_crcbdelay = flag_crcbmode = 0;
arg_crcbmode = IW44Image::CRCBfull;
}
else if (argv[i] == "-crcbdelay")
{
if (++i >= argc)
G_THROW( ERR_MSG("c44.no_crcbdelay_arg") );
if (flag_crcbdelay>=0)
G_THROW( ERR_MSG("c44.incompatable_chrominance") );
char *ptr;
flag_crcbdelay = strtol(argv[i], &ptr, 10);
if (*ptr || flag_crcbdelay<0 || flag_crcbdelay>=100)
G_THROW( ERR_MSG("c44.illegal_crcbdelay") );
}
else if (argv[i] == "-dpi")
{
if (++i >= argc)
G_THROW( ERR_MSG("c44.no_dpi_arg") );
if (flag_dpi>0)
G_THROW( ERR_MSG("c44.duplicate_dpi") );
char *ptr;
flag_dpi = strtol(argv[i], &ptr, 10);
if (*ptr || flag_dpi<25 || flag_dpi>4800)
G_THROW( ERR_MSG("c44.illegal_dpi") );
}
else if (argv[i] == "-gamma")
{
if (++i >= argc)
G_THROW( ERR_MSG("c44.no_gamma_arg") );
if (flag_gamma > 0)
G_THROW( ERR_MSG("c44.duplicate_gamma") );
char *ptr;
flag_gamma = strtod(argv[i], &ptr);
if (*ptr || flag_gamma<=0.25 || flag_gamma>=5)
G_THROW( ERR_MSG("c44.illegal_gamma") );
}
else
usage();
}
else if (g().pnmurl.is_empty())
g().pnmurl = GURL::Filename::UTF8(argv[i]);
else if (g().iw4url.is_empty())
g().iw4url = GURL::Filename::UTF8(argv[i]);
else
usage();
}
if (g().pnmurl.is_empty())
usage();
if (g().iw4url.is_empty())
{
GURL codebase=g().pnmurl.base();
GUTF8String base = g().pnmurl.fname();
int dot = base.rsearch('.');
if (dot >= 1)
base = base.substr(0,dot);
const char *ext=".djvu";
g().iw4url = GURL::UTF8(base+ext,codebase);
}
}
GP<GBitmap>
getmask(int w, int h)
{
GP<GBitmap> msk8;
if (! g().mskurl.is_empty())
{
GP<ByteStream> mbs=ByteStream::create(g().mskurl,"rb");
msk8 = GBitmap::create(*mbs);
if (msk8->columns() != (unsigned int)w ||
msk8->rows() != (unsigned int)h )
G_THROW( ERR_MSG("c44.different_size") );
}
return msk8;
}
static void
create_photo_djvu_file(IW44Image &iw, int w, int h,
IFFByteStream &iff, int nchunks, IWEncoderParms xparms[])
{
// Prepare info chunk
GP<DjVuInfo> ginfo=DjVuInfo::create();
DjVuInfo &info=*ginfo;
info.width = w;
info.height = h;
info.dpi = (flag_dpi>0 ? flag_dpi : 100);
info.gamma = (flag_gamma>0 ? flag_gamma : 2.2);
// Write djvu header and info chunk
iff.put_chunk("FORM:DJVU", 1);
iff.put_chunk("INFO");
info.encode(*iff.get_bytestream());
iff.close_chunk();
// Write all chunks
int flag = 1;
for (int i=0; flag && i<nchunks; i++)
{
iff.put_chunk("BG44");
flag = iw.encode_chunk(iff.get_bytestream(), xparms[i]);
iff.close_chunk();
}
// Close djvu chunk
iff.close_chunk();
}
int
main(int argc, char **argv)
{
DJVU_LOCALE;
GArray<GUTF8String> dargv(0,argc-1);
for(int i=0;i<argc;++i)
dargv[i]=GNativeString(argv[i]);
G_TRY
{
// Parse arguments
parse(dargv);
// Check input file
GP<ByteStream> gibs=ByteStream::create(g().pnmurl,"rb");
ByteStream &ibs=*gibs;
char prefix[16];
memset(prefix, 0, sizeof(prefix));
if (ibs.readall((void*)prefix, sizeof(prefix)) < sizeof(prefix))
G_THROW( ERR_MSG("c44.failed_pnm_header") );
#ifdef DEFAULT_JPEG_TO_HALF_SIZE
// Default specification for jpeg files
// This is disabled because
// -1- jpeg detection is unreliable.
// -2- quality is very difficult to predict.
if(prefix[0]!='P' &&prefix[0]!='A' && prefix[0]!='F' &&
!flag_mask && !flag_bpp && !flag_size &&
!flag_slice && !flag_decibel)
{
parse_size("10,20,30,50");
flag_size = flag_percent = 1;
}
#endif
// Change percent specification into size specification
if (flag_size && flag_percent)
for (int i=0; i<argc_size; i++)
argv_size[i] = (argv_size[i]*gibs->size())/ 100;
flag_percent = 0;
// Load images
int w = 0;
int h = 0;
ibs.seek(0);
GP<IW44Image> iw;
// Check color vs gray
if (prefix[0]=='P' && (prefix[1]=='2' || prefix[1]=='5'))
{
// gray file
GP<GBitmap> gibm=GBitmap::create(ibs);
GBitmap &ibm=*gibm;
w = ibm.columns();
h = ibm.rows();
iw = IW44Image::create_encode(ibm, getmask(w,h));
}
else if (!GStringRep::cmp(prefix,"AT&TFORM",8) ||
!GStringRep::cmp(prefix,"FORM",4))
{
char *s = (prefix[0]=='F' ? prefix+8 : prefix+12);
GP<IFFByteStream> giff=IFFByteStream::create(gibs);
IFFByteStream &iff=*giff;
const bool color=!GStringRep::cmp(s,"PM44",4);
if (color || !GStringRep::cmp(s,"BM44",4))
{
iw = IW44Image::create_encode(IW44Image::COLOR);
iw->decode_iff(iff);
w = iw->get_width();
h = iw->get_height();
}
else
G_THROW( ERR_MSG("c44.unrecognized") );
// Check that no mask has been specified.
if (! g().mskurl.is_empty())
G_THROW( ERR_MSG("c44.failed_mask") );
}
else // just for kicks, try jpeg.
{
// color file
const GP<GPixmap> gipm(GPixmap::create(ibs));
GPixmap &ipm=*gipm;
w = ipm.columns();
h = ipm.rows();
iw = IW44Image::create_encode(ipm, getmask(w,h), arg_crcbmode);
}
// Call destructor on input file
gibs=0;
// Perform compression PM44 or BM44 as required
if (iw)
{
g().iw4url.deletefile();
GP<IFFByteStream> iff =
IFFByteStream::create(ByteStream::create(g().iw4url,"wb"));
if (flag_crcbdelay >= 0)
iw->parm_crcbdelay(flag_crcbdelay);
if (flag_dbfrac > 0)
iw->parm_dbfrac((float)flag_dbfrac);
int nchunk = resolve_quality(w*h);
// Create djvu file
create_photo_djvu_file(*iw, w, h, *iff, nchunk, g().parms);
}
}
G_CATCH(ex)
{
ex.perror();
exit(1);
}
G_ENDCATCH;
return 0;
}