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); }
static void nofile(char *s) { DjVuPrintErrorUTF8("Error: File '%s' does not exist.\n",s); exit(1); }
static void usage(char *argv0) { DjVuPrintErrorUTF8("Usage: %s [-o <djvufile>] <xmlfile> ...\n", argv0); exit(1); }
int main(int argc, char **argv) { setlocale(LC_ALL,""); setlocale(LC_NUMERIC,"C"); djvu_programname(argv[0]); GArray<GUTF8String> dargv(0,argc-1); for(int i=0;i<argc;++i) dargv[i]=GNativeString(argv[i]); G_TRY { // Print usage when called without enough arguments if (argc <= 2) usage(); // Open djvu file remove(dargv[1]); GP<IFFByteStream> 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<argc; i++) { if (!dargv[i].cmp("INFO=",5)) { if (i>2) DjVuPrintErrorUTF8("%s","djvumake: 'INFO' chunk should appear first (ignored)\n"); } else if (!dargv[i].cmp("Sjbz=",5)) { if (flag_contains_stencil) DjVuPrintErrorUTF8("%s","djvumake: duplicate stencil chunk\n"); create_jb2_chunk(iff, "Sjbz", GURL::Filename::UTF8(5+(const char *)dargv[i])); flag_contains_stencil = 1; if (flag_fg_needs_palette && blit_count >= 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<GPixmap> inputsub=GPixmap::create((h+11)/12, (w+11)/12, &bgcolor); GP<IW44Image> 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; }
void create_bg44_chunk(IFFByteStream &iff, const char *ckid, GUTF8String filespec) { static GP<IFFByteStream> 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<ByteStream> 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<ByteStream> 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"); }
// -- Compresses low color pixmap. void cpaldjvu(ByteStream *ibs, GURL &urlout, const cpaldjvuopts &opts) { GP<GPixmap> ginput=GPixmap::create(*ibs); int w = ginput->columns(); int h = ginput->rows(); int dpi = MAX(200, MIN(900, opts.dpi)); int largesize = MIN(500, MAX(64, dpi)); int smallsize = MAX(2, dpi/150); // Compute optimal palette and quantize input pixmap GP<DjVuPalette> gpal=DjVuPalette::create(); DjVuPalette &pal=*gpal; GPixel bgcolor; int bgindex = -1; if (! opts.bgwhite) { bgindex = pal.compute_pixmap_palette(*ginput, opts.ncolors); pal.index_to_color(bgindex, bgcolor); } else { bgcolor = GPixel::WHITE; pal.histogram_clear(); for (int j=0; j<h; j++) { const GPixel *p = (*ginput)[j]; for (int i=0; i<w; i++) if (p[i] != GPixel::WHITE) pal.histogram_add(p[i], 1); } pal.compute_palette(opts.ncolors); } if (opts.verbose) DjVuFormatErrorUTF8( "%s\t%d\t%d\t%d", ERR_MSG("cpaldjvu.quantizied"), w, h, pal.size()); if (opts.verbose && !opts.bgwhite) DjVuPrintErrorUTF8("cpaldjvu: background color is #%02x%02x%02x.\n", bgcolor.r, bgcolor.g, bgcolor.b); // Fill CCImage with color runs int xruncount=0,yruncount=0; CCImage rimg(w, h); int *line; GPBuffer<int> gline(line,w); int *prevline; GPBuffer<int> gprevline(prevline,w); for (int x=0;x<w;x++) { prevline[x]=bgindex; } for (int y=0; y<h; y++) { int x; const GPixel *row = (*ginput)[y]; for(x=0;x<w;x++) { line[x] = pal.color_to_index(row[x]); if (opts.bgwhite && row[x]==GPixel::WHITE) line[x] = bgindex; } for(x=0;x<w;) { int x1 = x; int index = line[x++]; while (x<w && line[x]==index) { x++; } if (index != bgindex) { xruncount++; rimg.add_single_run(y, x1, x-1, index); } } for(x=0;x<w;x++) if(prevline[x] != line[x]) yruncount++; gprevline.swap(gline); } ginput = 0; //save memory if (opts.verbose) DjVuFormatErrorUTF8( "%s\t%d", ERR_MSG("cpaldjvu.color_runs"), rimg.runs.size()); // Perform Color Connected Component Analysis rimg.make_ccids_by_analysis(); // Obtain ccids rimg.make_ccs_from_ccids(); // Compute cc descriptors if (opts.verbose) DjVuFormatErrorUTF8( "%s\t%d", ERR_MSG("cpaldjvu.ccs_before"), rimg.ccs.size()); rimg.merge_and_split_ccs(smallsize,largesize); // Eliminates gross ccs if (opts.verbose) DjVuFormatErrorUTF8( "%s\t%d", ERR_MSG("cpaldjvu.ccs_after"), rimg.ccs.size()); rimg.sort_in_reading_order(); // Sort cc descriptors // Create JB2Image and fill colordata GP<JB2Image> gjimg=JB2Image::create(); JB2Image &jimg=*gjimg; jimg.set_dimension(w, h); int nccs = rimg.ccs.size(); for (int ccid=0; ccid<nccs; ccid++) { JB2Shape shape; JB2Blit blit; shape.parent = -1; shape.userdata = 0; if (ccid >= rimg.nregularccs) shape.userdata |= JB2SHAPE_SPECIAL; shape.bits = rimg.get_bitmap_for_cc(ccid); shape.bits->compress(); CC& cc = rimg.ccs[ccid]; blit.shapeno = jimg.add_shape(shape); blit.left = cc.bb.xmin; blit.bottom = cc.bb.ymin; int blitno = jimg.add_blit(blit); pal.colordata.touch(blitno); pal.colordata[blitno] = cc.color; } // Organize JB2Image tune_jb2image_lossless(&jimg); if (opts.verbose) { int nshape=0, nrefine=0; for (int i=0; i<jimg.get_shape_count(); i++) { if (!jimg.get_shape(i).bits) continue; if (jimg.get_shape(i).parent >= 0) nrefine++; nshape++; } DjVuFormatErrorUTF8( "%s\t%d\t%d", ERR_MSG("cpaldjvu.cross_code"), nshape, nrefine); } // Create background image #ifdef BACKGROUND_SUBSAMPLING_FACTOR // -- we may create the background by masking and subsampling GP<GPixmap> ginputsub=GPixmap::create(); GPixmap &inputsub=*ginputsub; GP<GBitmap> mask = jimg.get_bitmap(BACKGROUND_SUBSAMPLING_FACTOR); inputsub.downsample(&input, BACKGROUND_SUBSAMPLING_FACTOR); GP<IW44Image> iwimage=IW44Image::create(inputsub, mask); #else // -- but who cares since the background is uniform. GP<GPixmap> ginputsub=GPixmap::create((h+11)/12, (w+11)/12, &bgcolor); GPixmap &inputsub=*ginputsub; GP<IW44Image> iwimage=IW44Image::create_encode(inputsub); #endif // Assemble DJVU file GP<ByteStream> obs=ByteStream::create(urlout, "wb"); GP<IFFByteStream> giff=IFFByteStream::create(obs); IFFByteStream &iff=*giff; // -- main composite chunk iff.put_chunk("FORM:DJVU", 1); // -- ``INFO'' chunk iff.put_chunk("INFO"); GP<DjVuInfo> ginfo=DjVuInfo::create(); DjVuInfo info=*ginfo; info.height = h; info.width = w; info.dpi = opts.dpi; info.encode(*iff.get_bytestream()); iff.close_chunk(); // -- ``Sjbz'' chunk iff.put_chunk("Sjbz"); jimg.encode(iff.get_bytestream()); iff.close_chunk(); // -- ``FGbz'' chunk iff.put_chunk("FGbz"); pal.encode(iff.get_bytestream()); iff.close_chunk(); // -- ``BG44'' chunk IWEncoderParms iwparms; #ifdef PROGRESSIVE_BACKGROUND // ----- we may use several chunks to enable progressive rendering ... iff.put_chunk("BG44"); iwparms.slices = 74; iwimage->encode_chunk(iff, iwparms); iff.close_chunk(); iff.put_chunk("BG44"); iwparms.slices = 87; iwimage->encode_chunk(iff, iwparms); iff.close_chunk(); #endif // ----- but who cares when the background is so small. iff.put_chunk("BG44"); iwparms.slices = 97; iwimage->encode_chunk(iff.get_bytestream(), iwparms); iff.close_chunk(); // -- terminate main composite chunk iff.close_chunk(); // Finished! }
void DjVuMessageLite::perror( const GUTF8String & MessageList ) { DjVuPrintErrorUTF8("%s\n",(const char *)DjVuMessageLite::LookUpUTF8(MessageList)); }
static void read_tiff(CCImage &rimg, ByteStream *bs, cjb2opts &opts) { TIFF *tiff = TIFFClientOpen("libtiff", "rm", (thandle_t)bs, readproc, writeproc, seekproc, closeproc, sizeproc, mapproc, unmapproc ); // bitonal uint16 bps = 0, spp = 0; TIFFGetFieldDefaulted(tiff, TIFFTAG_BITSPERSAMPLE, &bps); TIFFGetFieldDefaulted(tiff, TIFFTAG_SAMPLESPERPIXEL, &spp); if (bps != 1 || spp != 1) G_THROW("Tiff image is not bitonal"); // photometric uint16 photo = PHOTOMETRIC_MINISWHITE; TIFFGetFieldDefaulted(tiff, TIFFTAG_PHOTOMETRIC, &photo); // image size uint32 w, h; if (!TIFFGetFieldDefaulted(tiff, TIFFTAG_IMAGEWIDTH, &w) || !TIFFGetFieldDefaulted(tiff, TIFFTAG_IMAGELENGTH, &h) ) G_THROW("Tiff image size is not defined"); // resolution float xres, yres; if (TIFFGetFieldDefaulted(tiff, TIFFTAG_XRESOLUTION, &xres) && TIFFGetFieldDefaulted(tiff, TIFFTAG_YRESOLUTION, &yres) ) { if (xres != yres) DjVuPrintErrorUTF8( "cjb2: X- and Y-resolution do not match\n"); if (! opts.forcedpi) opts.dpi = (int) (xres + yres) / 2; } // init rimg rimg.init(w, h, opts.dpi); // allocate scanline tsize_t scanlinesize = TIFFScanlineSize(tiff); scanlinesize = MAX(scanlinesize,1); unsigned char *scanline = 0; GPBuffer<unsigned char> gscanline(scanline, scanlinesize); // iterate on rows for (int y=0; y<(int)h; y++) { int yy = h - y - 1; if (TIFFReadScanline(tiff, (tdata_t)scanline, y) < 0) G_THROW("Tiff file is corrupted (TIFFReadScanline)"); if (photo != PHOTOMETRIC_MINISWHITE) for (int i=0; i<(int)scanlinesize; i++) scanline[i] ^= 0xff; int lastx=0, off=0; unsigned char mask=0, c=0, b=0; for (int x=0; x<(int)w; x++) { if (! mask) { b = scanline[off++]; while (b==c && x+8<(int)w ) { x = x + 8; // speedup b = scanline[off++]; } mask = 0x80; } if ( (b ^ c) & mask ) { c ^= 0xff; if (c) lastx = x; else rimg.add_single_run(yy, lastx, x-1); } mask >>= 1; } if (c) rimg.add_single_run(yy, lastx, w-1); } // close TIFFClose(tiff); }