//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 djvumake {\bf Synopsis} \begin{verbatim} % djvumake [......] \end{verbatim} {\bf Description} This command assembles a single-page DjVu file by copying or creating chunks according to the provided . Supported syntaxes for are as follows: \begin{tabular}{ll} {#INFO=,,#} & Creates the initial ``INFO'' chunk. Arguments #w#, #h# and #dpi# describe the width, height and resolution of the image. All arguments may be omitted. The default resolution is 300 dpi. The default width and height will be retrieved from the first mask chunk specified in the command line options.\\ {#Sjbz=#} & Creates a JB2 mask chunk. File #jb2file# may contain raw JB2 data or be a DjVu file containing JB2 data, such as the files produced by \Ref{cjb2}.\\ {#Smmr=] #} & Creates a mask chunk containing MMR/G4 data. File #mmrfile# may contain raw MMR data or be a DjVu file containing MMR data.\\ {#BG44=:#} & Creates one or more IW44 background chunks. File #iw44file# must be an IW44 file such as the files created by \Ref{c44}. The optional argument #n# indicates the number of chunks to copy from the IW44 file.\\ {#BGjp=#} & Creates a JPEG background chunk.\\ {#BG2k=#} & Creates a JPEG-2000 background chunk.\\ {#FG44=#} & Creates one IW44 foreround chunks. File #iw44file# must be an IW44 file such as the files created by \Ref{c44}. Only the first chunk will be copied.\\ {FGbz=(bzzfile|\{#color1[:x,y,w,h]\})} Creates a chunk containing colors for each JB2 encoded object. Such chunks are created using class \Ref{DjVuPalette}. See program \Ref{cpaldjvu} for an example.\\ {#FGjp=#} & Creates a JPEG foreground chunk.\\ {#FG2k=#} & Creates a JPEG-2000 foreground chunk.\\ {#INCL=#} & Creates an include chunk pointing to . The resulting file should then be included into a multipage document.\\ {#PPM=#} (psuedo-chunk) & Create IW44 foreground and background chunks by masking and subsampling PPM file #ppmfile#. This is used by program \Ref{cdjvu}.\\ {#chunk=#} & Creates the specified chunk with the specified raw data. \end{tabular} Let us assume now that you have a PPM image #"myimage.ppm"# and a PBM bitonal image #"mymask.pbm"# whose black pixels indicate which pixels belong to the foreground. Such a bitonal file may be obtained by thresholding, although more sophisticated techniques can give better results. You can then generate a Compound DjVu File by typing: \begin{verbatim} % cjb2 mymask.pbm mymask.djvu % djvumake mycompound.djvu Sjbz=mymask.djvu PPM=myimage.ppm \end{verbatim} @memo Assemble DjVu files. @author L\'eon Bottou \\ Patrick Haffner */ //@{ //@} #include "GString.h" #include "GException.h" #include "DjVuImage.h" #include "MMRDecoder.h" #include "IFFByteStream.h" #include "JB2Image.h" #include "IW44Image.h" #include "GPixmap.h" #include "GBitmap.h" #include "GContainer.h" #include "GRect.h" #include "DjVuMessage.h" #include "common.h" int flag_contains_fg = 0; int flag_contains_bg = 0; int flag_contains_stencil = 0; int flag_contains_incl = 0; int flag_fg_needs_palette = 0; struct DJVUMAKEGlobal { // Globals that need static initialization // are grouped here to work around broken compilers. GP jb2stencil; GP mmrstencil; GP stencil; GP dictionary; GTArray colorzones; GP colorpalette; }; static DJVUMAKEGlobal& g(void) { static DJVUMAKEGlobal g; return g; } int w = -1; int h = -1; int dpi = 300; int blit_count = -1; // -- Display brief usage information void usage() { DjVuPrintErrorUTF8( #ifdef DJVULIBRE_VERSION "DJVUMAKE --- DjVuLibre-" DJVULIBRE_VERSION "\n" #endif "Utility for manually assembling DjVu files\n\n" "Usage: djvumake djvufile ...arguments...\n" "\n" "The arguments describe the successive chunks of the DJVU file.\n" "Possible arguments are:\n" "\n" " INFO=w[,[h[,[dpi]]]] -- Create the initial information chunk\n" " Sjbz=jb2file -- Create a JB2 mask chunk\n" " Djbz=jb2file -- Create a JB2 shape dictionary\n" " Smmr=mmrfile -- Create a MMR mask chunk\n" " BG44=[iw4file][:nchunks] -- Create one or more IW44 background chunks\n" " BGjp=jpegfile -- Create a JPEG background chunk\n" " BG2k=jpeg2000file -- Create a JP2K background chunk\n" " FG44=iw4file -- Create an IW44 foreground chunk\n" " FGbz=bzzfile -- Create a foreground color chunk from a file\n" " FGbz={#color:x,y,w,h} -- Create a foreground color chunk from zones\n" " FGjp=jpegfile -- Create a JPEG foreground image chunk\n" " FG2k=jpeg2000file -- Create a JP2K foreground image chunk\n" " INCL=fileid -- Create an INCL chunk\n" " chunk=rawdatafile -- Create the specified chunk from the raw data file\n" " PPM=ppmfile -- Create IW44 foreground and background chunks\n" " by masking and subsampling a PPM file.\n" "\n" "You may omit the specification of the information chunk. An information\n" "chunk will be created using the image size of the first mask chunk\n" "This program is sometimes able to issue a warning when you are building an\n" "incorrect djvu file.\n" "\n"); exit(1); } // -- Obtain image size from mmr chunk void analyze_mmr_chunk(const GURL &url) { if (!g().mmrstencil || !g().mmrstencil->size()) { GP gbs=ByteStream::create(url,"rb"); ByteStream &bs=*gbs; g().mmrstencil = ByteStream::create(); // Check if file is an IFF file char magic[4]; memset(magic,0,sizeof(magic)); bs.readall(magic,sizeof(magic)); if (!GStringRep::cmp(magic,"AT&T",4)) bs.readall(magic,sizeof(magic)); if (GStringRep::cmp(magic,"FORM",4)) { // Must be a raw file bs.seek(0); g().mmrstencil->copy(bs); } else { // Search Smmr chunk bs.seek(0); GUTF8String chkid; GP giff=IFFByteStream::create(gbs); IFFByteStream &iff=*giff; if (iff.get_chunk(chkid)==0 || chkid!="FORM:DJVU") G_THROW("Expecting a DjVu file!"); for(; iff.get_chunk(chkid); iff.close_chunk()) if (chkid=="Smmr") { g().mmrstencil->copy(bs); break; } } // Check result g().mmrstencil->seek(0); if (!g().mmrstencil->size()) G_THROW("Could not find MMR data"); // Decode g().stencil = MMRDecoder::decode(g().mmrstencil); int jw = g().stencil->get_width(); int jh = g().stencil->get_height(); if (w < 0) w = jw; if (h < 0) h = jh; if (jw!=w || jh!=h) DjVuPrintErrorUTF8("djvumake: mask size (%s) does not match info size\n", (const char *)url); } } // -- Obtain shape dictionary void analyze_djbz_chunk(GP gbs) { if (g().dictionary) G_THROW("Duplicate Djbz dictionary"); g().dictionary = JB2Dict::create(); g().dictionary->decode(gbs); } void analyze_djbz_chunk(const GURL &url) { GP gbs = ByteStream::create(url, "rb"); analyze_djbz_chunk(gbs); } // -- Obtain image size and blit count from jb2 chunk GP provide_shared_dict( void* ) { return(g().dictionary); } void analyze_jb2_chunk(const GURL &url) { if (!g().jb2stencil || !g().jb2stencil->size()) { GP gbs=ByteStream::create(url,"rb"); ByteStream &bs=*gbs; g().jb2stencil = ByteStream::create(); // Check if file is an IFF file char magic[4]; memset(magic,0,sizeof(magic)); bs.readall(magic,sizeof(magic)); if (!GStringRep::cmp(magic,"AT&T",4)) bs.readall(magic,sizeof(magic)); if (GStringRep::cmp(magic,"FORM",4)) { // Must be a raw file bs.seek(0); g().jb2stencil->copy(bs); } else { // Search Sjbz chunk bs.seek(0); GUTF8String chkid; GP giff=IFFByteStream::create(gbs); IFFByteStream &iff=*giff; if (iff.get_chunk(chkid)==0 || chkid!="FORM:DJVU") G_THROW("Expecting a DjVu file!"); for(; iff.get_chunk(chkid); iff.close_chunk()) if (chkid=="Sjbz") { g().jb2stencil->copy(bs); break; } } // Check result g().jb2stencil->seek(0); if (!g().jb2stencil->size()) G_THROW("Could not find JB2 data"); // Decode g().stencil=JB2Image::create(); g().stencil->decode(g().jb2stencil,&provide_shared_dict,NULL); int jw = g().stencil->get_width(); int jh = g().stencil->get_height(); if (w < 0) w = jw; if (h < 0) h = jh; if (blit_count < 0) blit_count = g().stencil->get_blit_count(); if (jw!=w || jh!=h) DjVuPrintErrorUTF8("djvumake: mask size (%s) does not match info size\n", (const char *)url); } } // -- Load dictionary from an INCL chunk void analyze_incl_chunk(const GURL &url) { GP gbs = ByteStream::create(url,"rb"); char buffer[24]; memset(buffer, 0, sizeof(buffer)); gbs->read(buffer,sizeof(buffer)); char *s = buffer; if (!strncmp(s, "AT&T", 4)) s += 4; if (strncmp(s, "FORM", 4) || strncmp(s+8, "DJVI", 4)) G_THROW("Expecting a valid FORM:DJVI chunk in the included file"); gbs->seek(0); GP giff=IFFByteStream::create(gbs); GUTF8String chkid; giff->get_chunk(chkid); // FORM:DJVI for(; giff->get_chunk(chkid); giff->close_chunk()) if (chkid=="Djbz") analyze_djbz_chunk(giff->get_bytestream()); } void check_for_shared_dict(GArray &argv) { const int argc=argv.hbound()+1; for (int i=2; i0 && (w<=0 || w>=32768)) G_THROW("djvumake: incorrect width in 'INFO' chunk specification\n"); if (h>0 && (h<=0 || h>=32768)) G_THROW("djvumake: incorrect height in 'INFO' chunk specification\n"); if (dpi>0 && (dpi<25 || dpi>6000)) G_THROW("djvumake: incorrect dpi in 'INFO' chunk specification\n"); // Search first mask chunks if size is still unknown if (h<0 || w<0) { for (int i=2; i ginfo=DjVuInfo::create(); DjVuInfo &info=*ginfo; info.width = w; info.height = h; info.dpi = dpi; iff.put_chunk("INFO"); info.encode(*iff.get_bytestream()); iff.close_chunk(); } // -- Create MMR mask chunk void create_mmr_chunk(IFFByteStream &iff, const char *chkid, const GURL &url) { analyze_mmr_chunk(url); g().mmrstencil->seek(0); iff.put_chunk(chkid); iff.copy(*g().mmrstencil); iff.close_chunk(); } // -- Create FGbz palette chunk void create_fgbz_chunk(IFFByteStream &iff) { int nzones = g().colorzones.size(); int npalette = g().colorpalette->size() / 3; GP pal = DjVuPalette::create(); g().colorpalette->seek(0); pal->decode_rgb_entries(*g().colorpalette, npalette); pal->colordata.resize(0,blit_count-1); for (int d=0; dget_blit(d); const JB2Shape &shape = g().stencil->get_shape(blit->shapeno); GRect brect(blit->left, blit->bottom, shape.bits->columns(), shape.bits->rows()); int index = nzones; for (int i=0; i= npalette) G_THROW("create_fgbz_chunk: internal error"); pal->colordata[d] = index; } iff.put_chunk("FGbz"); pal->encode(iff.get_bytestream()); iff.close_chunk(); } // -- Create JB2 mask chunk void create_jb2_chunk(IFFByteStream &iff, const char *chkid, const GURL &url) { analyze_jb2_chunk(url); g().jb2stencil->seek(0); iff.put_chunk(chkid); iff.copy(*g().jb2stencil); iff.close_chunk(); } // -- Create inclusion chunk void create_incl_chunk(IFFByteStream &iff, const char *chkid, const char *fileid) { iff.put_chunk("INCL"); iff.write(fileid, strlen(fileid)); iff.close_chunk(); } // -- Create chunk by copying file contents void create_raw_chunk(IFFByteStream &iff, const GUTF8String &chkid, const GURL &url) { iff.put_chunk(chkid); GP ibs=ByteStream::create(url,"rb"); iff.copy(*ibs); iff.close_chunk(); } // -- Internal headers for IW44 struct PrimaryHeader { unsigned char serial; unsigned char slices; } primary; struct SecondaryHeader { unsigned char major; unsigned char minor; unsigned char xhi, xlo; unsigned char yhi, ylo; } secondary; // -- Create and check FG44 chunk void create_fg44_chunk(IFFByteStream &iff, const char *ckid, const GURL &url) { GP gbs=ByteStream::create(url,"rb"); GP gbsi=IFFByteStream::create(gbs); IFFByteStream &bsi=*gbsi; GUTF8String chkid; bsi.get_chunk(chkid); if (chkid != "FORM:PM44" && chkid != "FORM:BM44") G_THROW("djvumake: FG44 file has incorrect format (wrong IFF header)"); bsi.get_chunk(chkid); if (chkid!="PM44" && chkid!="BM44") G_THROW("djvumake: FG44 file has incorrect format (wring IFF header)"); GP gmbs=ByteStream::create(); ByteStream &mbs=*gmbs; mbs.copy(*bsi.get_bytestream()); bsi.close_chunk(); if (bsi.get_chunk(chkid)) DjVuPrintErrorUTF8("%s","djvumake: FG44 file contains more than one chunk\n"); bsi.close_chunk(); mbs.seek(0); if (mbs.readall((void*)&primary, sizeof(primary)) != sizeof(primary)) G_THROW("djvumake: FG44 file is corrupted (cannot read primary header)"); if (primary.serial != 0) G_THROW("djvumake: FG44 file is corrupted (wrong serial number)"); if (mbs.readall((void*)&secondary, sizeof(secondary)) != sizeof(secondary)) G_THROW("djvumake: FG44 file is corrupted (cannot read secondary header)"); int iw = (secondary.xhi<<8) + secondary.xlo; int ih = (secondary.yhi<<8) + secondary.ylo; int red; for (red=1; red<=12; red++) if (iw==(w+red-1)/red && ih==(h+red-1)/red) break; flag_contains_fg = red; if (red>12) DjVuPrintErrorUTF8("%s","djvumake: FG44 subsampling is not in [1..12] range\n"); mbs.seek(0); iff.put_chunk(ckid); iff.copy(mbs); iff.close_chunk(); } // -- Create and check BG44 chunk void create_bg44_chunk(IFFByteStream &iff, const char *ckid, GUTF8String filespec) { static GP bg44iff; if (! bg44iff) { if (flag_contains_bg) DjVuPrintErrorUTF8("%s","djvumake: Duplicate BGxx chunk\n"); int i=filespec.rsearch(':'); for (int j=i+1; i>0 && j<(int)filespec.length(); j++) if (filespec[j] < '0' || filespec[j] > '9') i = -1; if (!i) G_THROW("djvumake: no filename specified in first BG44 specification"); GUTF8String filename=(i<0)?filespec:GUTF8String(filespec, i); const GURL::Filename::UTF8 url(filename); const GP gbs(ByteStream::create(url,"rb")); if(!gbs) { G_THROW("djvumake: no such file as"+filename); } bg44iff = IFFByteStream::create(gbs); GUTF8String chkid; bg44iff->get_chunk(chkid); if (chkid != "FORM:PM44" && chkid != "FORM:BM44") G_THROW("djvumake: BG44 file has incorrect format (wrong IFF header)"); if (i>=0) filespec = i+1+(const char *)filespec; else filespec = "99"; } else { if (filespec.length() && filespec[0]!=':') G_THROW("djvumake: filename specified in BG44 refinement"); filespec = 1+(const char *)filespec; } const char *s=filespec; int nchunks = strtol((char *)s, (char **)&s, 10); if (nchunks<1 || nchunks>99) G_THROW("djvumake: invalid number of chunks in BG44 specification"); if (*s) G_THROW("djvumake: invalid BG44 specification (syntax error)"); int flag = (nchunks>=99); GUTF8String chkid; while (nchunks-->0 && bg44iff->get_chunk(chkid)) { if (chkid!="PM44" && chkid!="BM44") { DjVuPrintErrorUTF8("%s","djvumake: BG44 file contains unrecognized chunks (fixed)\n"); nchunks += 1; bg44iff->close_chunk(); continue; } GP gmbs=ByteStream::create(); ByteStream &mbs=*gmbs; mbs.copy(*(bg44iff->get_bytestream())); bg44iff->close_chunk(); mbs.seek(0); if (mbs.readall((void*)&primary, sizeof(primary)) != sizeof(primary)) G_THROW("djvumake: BG44 file is corrupted (cannot read primary header)\n"); if (primary.serial == 0) { if (mbs.readall((void*)&secondary, sizeof(secondary)) != sizeof(secondary)) G_THROW("djvumake: BG44 file is corrupted (cannot read secondary header)\n"); int iw = (secondary.xhi<<8) + secondary.xlo; int ih = (secondary.yhi<<8) + secondary.ylo; int red; for (red=1; red<=12; red++) if (iw==(w+red-1)/red && ih==(h+red-1)/red) break; flag_contains_bg = red; if (red>12) DjVuPrintErrorUTF8("%s","djvumake: BG44 subsampling is not in [1..12] range\n"); } mbs.seek(0); iff.put_chunk(ckid); iff.copy(mbs); iff.close_chunk(); flag = 1; } if (!flag) DjVuPrintErrorUTF8("%s","djvumake: no more chunks in BG44 file\n"); } // -- Forward declarations void processForeground(const GPixmap* image, const JB2Image *mask, GPixmap& subsampled_image, GBitmap& subsampled_mask); void processBackground(const GPixmap* image, const JB2Image *mask, GPixmap& subsampled_image, GBitmap& subsampled_mask); // -- Create both foreground and background by masking and subsampling void create_masksub_chunks(IFFByteStream &iff, const GURL &url) { // Check and load pixmap file if (!g().stencil) G_THROW("The use of a raw ppm image requires a stencil"); GP gibs=ByteStream::create(url, "rb"); ByteStream &ibs=*gibs; GP graw_pm=GPixmap::create(ibs); GPixmap &raw_pm=*graw_pm; if ((int) g().stencil->get_width() != (int) raw_pm.columns()) G_THROW("Stencil and raw image have different widths!"); if ((int) g().stencil->get_height() != (int) raw_pm.rows()) G_THROW("Stencil and raw image have different heights!"); // Encode foreground { GP gfg_img=GPixmap::create(); GPixmap &fg_img=*gfg_img; GP fg_mask=GBitmap::create(); processForeground(&raw_pm, g().stencil, fg_img, *fg_mask); GP fg_pm = IW44Image::create_encode(fg_img, fg_mask, IW44Image::CRCBfull); IWEncoderParms parms[8]; iff.put_chunk("FG44"); parms[0].slices = 100; fg_pm->encode_chunk(iff.get_bytestream(), parms[0]); iff.close_chunk(); } // Encode backgound { GP gbg_img=GPixmap::create(); GPixmap &bg_img=*gbg_img; GP bg_mask=GBitmap::create(); processBackground(&raw_pm, g().stencil, bg_img, *bg_mask); GP bg_pm = IW44Image::create_encode(bg_img, bg_mask, IW44Image::CRCBnormal); IWEncoderParms parms[4]; parms[0].bytes = 10000; parms[0].slices = 74; iff.put_chunk("BG44"); bg_pm->encode_chunk(iff.get_bytestream(), parms[0]); iff.close_chunk(); parms[1].slices = 84; iff.put_chunk("BG44"); bg_pm->encode_chunk(iff.get_bytestream(), parms[1]); iff.close_chunk(); parms[2].slices = 90; iff.put_chunk("BG44"); bg_pm->encode_chunk(iff.get_bytestream(), parms[2]); iff.close_chunk(); parms[3].slices = 97; iff.put_chunk("BG44"); bg_pm->encode_chunk(iff.get_bytestream(), parms[3]); iff.close_chunk(); } } const char * parse_color_name(const char *s, char *rgb) { static struct { const char *name; unsigned char r, g, b; } stdcols[] = { {"aqua", 0x00, 0xFF, 0xFF}, {"black", 0x00, 0x00, 0x00}, {"blue", 0x00, 0x00, 0xFF}, {"fuchsia", 0xFF, 0x00, 0xFF}, {"gray", 0x80, 0x80, 0x80}, {"green", 0x00, 0x80, 0x00}, {"lime", 0x00, 0xFF, 0x00}, {"maroon", 0x80, 0x00, 0x00}, {"navy", 0x00, 0x00, 0x80}, {"olive", 0x80, 0x80, 0x00}, {"purple", 0x80, 0x00, 0x80}, {"red", 0xFF, 0x00, 0x00}, {"silver", 0xC0, 0xC0, 0xC0}, {"teal", 0x00, 0x80, 0x80}, {"white", 0xFF, 0xFF, 0xFF}, {"yellow", 0xFF, 0xFF, 0x00}, {0} }; // potential color names int len = 0; while (s[len] && s[len]!=':' && s[len]!='#') len += 1; GUTF8String name(s, len); name = name.downcase(); for (int i=0; stdcols[i].name; i++) if (name == stdcols[i].name) { rgb[0] = stdcols[i].r; rgb[1] = stdcols[i].g; rgb[2] = stdcols[i].b; return s+len; } // potential hex specifications unsigned int r,g,b; if (sscanf(s,"%2x%2x%2x",&r,&g,&b) == 3) { rgb[0] = r; rgb[1] = g; rgb[2] = b; return s+6; } G_THROW("Unrecognized color name in FGbz chunk specification"); return 0; // win } void parse_color_zones(const char *s) { bool fullpage = false; int zones = 0; g().colorzones.empty(); g().colorpalette = ByteStream::create(); // zones while (s[0] == '#') { char rgb[3]; GRect rect; s = parse_color_name(s+1, rgb); if (s[0] == ':') { int c[4]; for (int i=0; i<4; i++) { char *e = 0; c[i] = strtol(s+1, &e, 10); if (e <= s || (i>=2 && c[i]<0) || (i<3 && e[0]!=',')) G_THROW("Invalid coordinates in FGbz chunk specification"); s = e; } rect = GRect(c[0],c[1],c[2],c[3]); } if (rect.isempty()) fullpage = true; g().colorpalette->writall(rgb, 3); g().colorzones.touch(zones); g().colorzones[zones] = rect; zones++; } if (s[0]) G_THROW("Syntax error in FGbz chunk specification"); // add extra black palette entry if (! fullpage) { char rgb[3] = {0,0,0}; g().colorpalette->writall(rgb, 3); } } // -- Main int main(int argc, char **argv) { DJVU_LOCALE; GArray dargv(0,argc-1); for(int i=0;i giff = IFFByteStream::create(ByteStream::create(GURL::Filename::UTF8(dargv[1]),"wb")); IFFByteStream &iff=*giff; // Create header iff.put_chunk("FORM:DJVU", 1); // Check if shared dicts are present check_for_shared_dict(dargv); // Create information chunk create_info_chunk(iff, dargv); // Parse all arguments for (int i=2; i= 0) create_fgbz_chunk(iff); flag_fg_needs_palette = 0; } else if (!dargv[i].cmp("Smmr=",5)) { create_mmr_chunk(iff, "Smmr", GURL::Filename::UTF8(5+(const char *)dargv[i])); if (flag_contains_stencil) DjVuPrintErrorUTF8("%s","djvumake: duplicate stencil chunk\n"); flag_contains_stencil = 1; } else if (!dargv[i].cmp("FGbz=",5)) { const char *c = 5 + (const char*)dargv[i]; if (flag_contains_fg) DjVuPrintErrorUTF8("%s","djvumake: duplicate 'FGxx' chunk\n"); if (c[0] != '#') { create_raw_chunk(iff, "FGbz", GURL::Filename::UTF8(c)); } else { parse_color_zones(c); if (flag_contains_stencil && blit_count >= 0) create_fgbz_chunk(iff); else flag_fg_needs_palette = 1; } flag_contains_fg = 1; } else if (!dargv[i].cmp("FG44=",5)) { if (flag_contains_fg) DjVuPrintErrorUTF8("%s","djvumake: duplicate 'FGxx' chunk\n"); create_fg44_chunk(iff, "FG44", GURL::Filename::UTF8(5+(const char *)dargv[i])); } else if (!dargv[i].cmp("BG44=",5)) { create_bg44_chunk(iff, "BG44", 5+(const char *)dargv[i]); } else if (!dargv[i].cmp("BGjp=",5) || !dargv[i].cmp("BG2k=",5) ) { if (flag_contains_bg) DjVuPrintErrorUTF8("%s","djvumake: Duplicate BGxx chunk\n"); GUTF8String chkid = dargv[i].substr(0,4); create_raw_chunk(iff, chkid, GURL::Filename::UTF8(5+(const char *)dargv[i])); flag_contains_bg = 1; } else if (!dargv[i].cmp("FGjp=",5) || !dargv[i].cmp("FG2k=",5)) { if (flag_contains_fg) DjVuPrintErrorUTF8("%s","djvumake: duplicate 'FGxx' chunk\n"); GUTF8String chkid = dargv[i].substr(0,4); create_raw_chunk(iff, chkid, GURL::Filename::UTF8(5+(const char *)dargv[i])); flag_contains_fg = 1; } else if (!dargv[i].cmp("INCL=",5)) { create_incl_chunk(iff, "INCL", GURL::Filename::UTF8(5+(const char *)dargv[i]).fname()); flag_contains_incl = 1; } else if (!dargv[i].cmp("PPM=",4)) { if (flag_contains_bg || flag_contains_fg) DjVuPrintErrorUTF8("%s","djvumake: Duplicate 'FGxx' or 'BGxx' chunk\n"); create_masksub_chunks(iff, GURL::Filename::UTF8(4+(const char *)dargv[i])); flag_contains_bg = 1; flag_contains_fg = 1; } else if (dargv[i].length() > 4 && dargv[i][4] == '=') { GNativeString chkid = dargv[i].substr(0,4); if (chkid != "TXTz" && chkid != "TXTa" && chkid != "ANTz" && chkid != "ANTa" && chkid != "Djbz" ) DjVuPrintErrorUTF8("djvumake: creating chunk of unknown type ``%s''.\n", (const char*)chkid); create_raw_chunk(iff, chkid, GURL::Filename::UTF8(5+(const char *)dargv[i])); } else { DjVuPrintErrorUTF8("djvumake: illegal argument : ``%s'' (ignored)\n", (const char *)dargv[i]); } } // Common cases for missing chunks if (flag_contains_stencil) { if (flag_contains_bg && ! flag_contains_fg) { DjVuPrintErrorUTF8("%s","djvumake: generating black FGbz chunk\n"); g().colorzones.empty(); g().colorpalette = ByteStream::create(); char rgb[3] = {0,0,0}; g().colorpalette->writall(rgb, 3); create_fgbz_chunk(iff); flag_contains_fg = 1; } if (flag_contains_fg && !flag_contains_bg) { DjVuPrintErrorUTF8("%s","djvumake: generating white BG44 chunk\n"); GPixel bgcolor = GPixel::WHITE; GP inputsub=GPixmap::create((h+11)/12, (w+11)/12, &bgcolor); GP iw = IW44Image::create_encode(*inputsub, 0, IW44Image::CRCBnone); IWEncoderParms iwparms; iff.put_chunk("BG44"); iwparms.slices = 97; iw->encode_chunk(iff.get_bytestream(), iwparms); iff.close_chunk(); flag_contains_bg = 1; } } // Close iff.close_chunk(); // Sanity checks if (flag_contains_stencil) { // Compound or Bilevel if (flag_contains_bg && ! flag_contains_fg) DjVuPrintErrorUTF8("%s","djvumake: djvu file contains a BGxx chunk but no FGxx chunk\n"); if (flag_contains_fg && ! flag_contains_bg) DjVuPrintErrorUTF8("%s","djvumake: djvu file contains a FGxx chunk but no BGxx chunk\n"); } else if (flag_contains_bg) { // Photo DjVu Image if (flag_contains_bg!=1) DjVuPrintErrorUTF8("%s","djvumake: photo djvu image has subsampled BGxx chunk\n"); if (flag_fg_needs_palette) DjVuPrintErrorUTF8("%s","djvumake: could not generate FGbz chunk, as stencil is not available\n"); else if (flag_contains_fg) DjVuPrintErrorUTF8("%s","djvumake: photo djvu file contains FGxx chunk\n"); } else DjVuPrintErrorUTF8("%s","djvumake: djvu file contains neither Sxxx nor BGxx chunks\n"); } G_CATCH(ex) { remove(dargv[1]); ex.perror(); exit(1); } G_ENDCATCH; return 0; } //////////////////////////////////////// // MASKING AND SUBSAMPLING //////////////////////////////////////// // -- Returns a dilated version of a bitmap with the same size static GP dilate8(const GBitmap *p_bm) { const GBitmap& bm = *p_bm; GP p_newbm = GBitmap::create(bm.rows(),bm.columns(),1); GBitmap& newbm = *p_newbm; int nrows = bm.rows(); int ncols = bm.columns(); for(int y=0; y=0) ? newbm[y-1] : 0; unsigned char *nbmrow = newbm[y]; unsigned char *nbmnrow = (y+1 erode8(const GBitmap *p_bm) { const GBitmap& bm = *p_bm; int newnrows = bm.rows()-2; int newncolumns = bm.columns()-2; if(newnrows<=0 || newncolumns<=0) // then return an empty GBitmap return GBitmap::create(); GP p_newbm = GBitmap::create(newnrows,newncolumns); GBitmap& newbm = *p_newbm; for(int y=0; y erode8(const JB2Image *im) { int i; GP newim = JB2Image::create(); newim->set_dimension(im->get_width(),im->get_height()); for(i=0; iget_shape_count(); i++) { const JB2Shape &shape = im->get_shape(i); JB2Shape newshape; newshape.parent = shape.parent; if (shape.bits) newshape.bits = erode8(shape.bits); else newshape.bits = 0; newim->add_shape(newshape); } for(i=0; iget_blit_count(); i++) { const JB2Blit* blit = im->get_blit(i); JB2Blit newblit; newblit.bottom = blit->bottom + 1; newblit.left = blit->left + 1; newblit.shapeno = blit->shapeno; newim->add_blit(newblit); } return newim; } // Subsamples only the pixels of that are not masked (). This // call resizes and fills the resulting and // . Their dimension is the dimension of the original // divided by and rounded to the superior integer. For // each square grid (gridwidth times gridwidth) of the subsampling mesh that // contains at least non-masked pixels, their value is averaged to // give the value of the corresponding pixel, and the // is cleared at this position. If is true, // then pixels are considered to be masked when mask==0 static void maskedSubsample(const GPixmap* img, const GBitmap *p_mask, GPixmap& subsampled_image, GBitmap& subsampled_mask, int gridwidth, int inverted_mask, int minpixels=1 ) { const GPixmap& image= *img; const GBitmap& mask = *p_mask; int imageheight = image.rows(); int imagewidth = image.columns(); // compute the size of the resulting subsampled image int subheight = imageheight/gridwidth; if(imageheight%gridwidth) subheight++; int subwidth = imagewidth/gridwidth; if(imagewidth%gridwidth) subwidth++; // set the sizes unless in incremental mode subsampled_image.init(subheight, subwidth); subsampled_mask.init(subheight, subwidth); // go subsampling int row, col; // row and col in the subsampled image int posx, posxend, posy, posyend; // corresponding square in the original image for(row=0, posy=0; rowimageheight) posyend = imageheight; for(col=0, posx=0; colimagewidth) posxend = imagewidth; int count = 0; int r = 0; int g = 0; int b = 0; for(int y=posy; y= minpixels) { GPixel p; p.r = r/count; p.g = g/count; p.b = b/count; subsampled_image_row[col] = p; subsampled_mask_row[col] = 0; } else /* make it bright red and masked */ { subsampled_image_row[col] = GPixel::RED; subsampled_mask_row[col] = 1; } } } } // -- Computes foreground image and mask void processForeground(const GPixmap* image, const JB2Image *mask, GPixmap& subsampled_image, GBitmap& subsampled_mask) { GP eroded_mask = erode8(mask); maskedSubsample(image, eroded_mask->get_bitmap(), subsampled_image, subsampled_mask, 6, 1); // foreground subsample is 6 (300dpi->50dpi) } // -- Computes background image and mask void processBackground(const GPixmap* image, const JB2Image *mask, GPixmap& subsampled_image, GBitmap& subsampled_mask) { GP b = mask->get_bitmap(); b = dilate8(b); b = dilate8(b); maskedSubsample(image, b, subsampled_image, subsampled_mask, 3, 0); }