#include "stdafx.h" #include #include #include #include #include #include #include "additionaltypes.h" #define u64 uint64_t #define u32 uint32_t #define u16 uint16_t #define u8 uint8_t #include "jbig2arith.h" #include "jbig2sym.h" #include "jbig2structs.h" #include "jbig2segments.h" // ----------------------------------------------------------------------------- // Removes spots which are less than size x size pixels // // Note, this has a side-effect of removing a few pixels // that from components you want to keep. // // If that's a problem, you do a binary reconstruction // (from seedfill.c): // ----------------------------------------------------------------------------- static PIX * remove_flyspecks(PIX *const source, const int size) { Sel *sel_5h = selCreateBrick(1, size, 0, 2, SEL_HIT); Sel *sel_5v = selCreateBrick(size, 1, 2, 0, SEL_HIT); Pix *pixt = pixOpen(NULL, source, sel_5h); Pix *pixd = pixOpen(NULL, source, sel_5v); pixOr(pixd, pixd, pixt); pixDestroy(&pixt); selDestroy(&sel_5h); selDestroy(&sel_5v); return pixd; } // ----------------------------------------------------------------------------- // Returns the number of bits needed to encode v symbols // ----------------------------------------------------------------------------- static unsigned log2up(int v) { unsigned r = 0; const bool is_pow_of_2 = (v & (v - 1)) == 0; while (v >>= 1) r++; if (is_pow_of_2) return r; return r + 1; } // ----------------------------------------------------------------------------- // This is the context for a multi-page JBIG2 document. // ----------------------------------------------------------------------------- struct jbig2ctx { struct JbClasser *classer; // the leptonica classifier int xres, yres; // ppi for the X and Y direction bool full_headers; // true if we are producing a full JBIG2 file bool pdf_page_numbering; // true if all text pages are page "1" (pdf mode) int segnum; // current segment number int symtab_segment; // the segment number of the symbol table // a map from page number a list of components for that page std::map > pagecomps; // for each page, the list of symbols which are only used on that page std::map > single_use_symbols; // the number of symbols in the global symbol table int num_global_symbols; std::vector page_width, page_height; // Used to store the mapping from symbol number to the index in the global // symbol dictionary. std::map symmap; bool refinement; PIXA *avg_templates; // grayed templates int refine_level; // only used when using refinement // the number of the first symbol of each page std::vector baseindexes; }; // see comments in .h file struct jbig2ctx * jbig2_init(float thresh, float weight, int xres, int yres, bool full_headers, int refine_level) { struct jbig2ctx *ctx = new jbig2ctx; ctx->xres = xres; ctx->yres = yres; ctx->full_headers = full_headers; ctx->pdf_page_numbering = !full_headers; ctx->segnum = 0; ctx->symtab_segment = -1; ctx->refinement = refine_level >= 0; ctx->refine_level = refine_level; ctx->avg_templates = NULL; ctx->classer = jbCorrelationInitWithoutComponents(JB_CONN_COMPS, 9999, 9999, thresh, weight); return ctx; } // see comments in .h file void jbig2_destroy(struct jbig2ctx *ctx) { if (ctx->avg_templates) pixaDestroy(&ctx->avg_templates); jbClasserDestroy(&ctx->classer); delete ctx; } // see comments in .h file void jbig2_add_page(struct jbig2ctx *ctx, struct Pix *input) { PIX *bw; if (false /*ctx->xres >= 300*/) { bw = remove_flyspecks(input, (int) (0.0084*ctx->xres)); } else { bw = pixClone(input); } if (ctx->refinement) { ctx->baseindexes.push_back(ctx->classer->baseindex); } jbAddPage(ctx->classer, bw); ctx->page_width.push_back(bw->w); ctx->page_height.push_back(bw->h); if (ctx->refinement) { // This code is broken by (my) recent changes to Leptonica. Needs to be // fixed at some point, but not too important at the moment since we don't // use refinement. /*BOXA *boxes = boxaCopy(ctx->classer->boxas, L_CLONE); ctx->boxes.push_back(boxes); PIXA *comps = pixaCopy(ctx->classer->pixas, L_CLONE); ctx->comps.push_back(comps);*/ } pixDestroy(&bw); } #define F(x) memcpy(ret + offset, &x, sizeof(x)) ; offset += sizeof(x) #define G(x, y) memcpy(ret + offset, x, y); offset += y; #define SEGMENT(x) x.write(ret + offset); offset += x.size(); // see comments in .h file uint8_t * jbig2_pages_complete(struct jbig2ctx *ctx, int *const length) { /* Graying support - disabled. It's not very clear that graying actaully buys you much extra quality above pick-the-first. Also, aligning the gray glyphs requires the original source image. Remember that you need the Init without WithoutComponents to use this */ /*NUMA *samples_per_composition; PTA *grayed_centroids; PIXA *grayed; grayed = jbAccumulateComposites(ctx->classer->pixaa, &samples_per_composition, &grayed_centroids); if (!grayed || grayed->n != ctx->classer->pixaa->n) { fprintf(stderr, "Graying failed\n"); return NULL; } ctx->avg_templates = pixaCreate(0); for (int i = 0; i < grayed->n; ++i) { int samples; numaGetIValue(samples_per_composition, i, &samples); PIX *avg = pixFinalAccumulateThreshold(grayed->pix[i], 0, (samples + 1) >> 1); pixaAddPix(ctx->avg_templates, avg, L_INSERT); //char b[512]; //sprintf(b, "gray-%d/th.png", i); //pixWrite(b, avg, IFF_PNG); } pixaDestroy(&grayed); numaDestroy(&samples_per_composition);*/ // We find the symbols which only appear on a single page and encode them in // a symbol dictionary just for that page. This is because we want to keep // the size of the global dictionary down as some PDF readers appear to // decode it for every page (!) // (as a short cut, we just pick the symbols which are only used once since, // in testing, all the symbols which appear on only one page appear only once // on that page) const bool single_page = ctx->classer->npages == 1; // maps symbol number to the number of times it has been used // pixat->n is the number of symbols // naclass->n is the number of connected components std::vector symbol_used(ctx->classer->pixat->n); for (int i = 0; i < ctx->classer->naclass->n; ++i) { int n; numaGetIValue(ctx->classer->naclass, i, &n); symbol_used[n]++; } // the multiuse symbols are the ones which go into the global dictionary std::vector multiuse_symbols; for (int i = 0; i < ctx->classer->pixat->n; ++i) { if (symbol_used[i] == 0) abort(); if (symbol_used[i] > 1 || single_page) multiuse_symbols.push_back(i); } ctx->num_global_symbols = multiuse_symbols.size(); // build the pagecomps map: a map from page number to the list of connected // components for that page. The classer gives us an array from connected // component number to page number - we just have to reverse it for (int i = 0; i < ctx->classer->napage->n; ++i) { int page_num; numaGetIValue(ctx->classer->napage, i, &page_num); ctx->pagecomps[page_num].push_back(i); int symbol; numaGetIValue(ctx->classer->naclass, i, &symbol); if (symbol_used[symbol] == 1 && !single_page) { ctx->single_use_symbols[page_num].push_back(symbol); } } #ifdef DUMP_SYMBOL_GRAPH for (int p = 0; p < ctx->classer->npages; ++p) { for (std::vector::const_iterator i = ctx->pagecomps[p].begin(); i != ctx->pagecomps[p].end(); ++i) { const int sym = (int) ctx->classer->naclass->array[*i]; fprintf(stderr, "S: %d %d\n", p, sym); } } #endif #ifdef SYMBOL_COMPRESSION_DEBUGGING std::map usecount; for (int i = 0; i < ctx->classer->naclass->n; ++i) { usecount[(int)ctx->classer->naclass->array[i]]++; } for (int p = 0; p < ctx->classer->npages; ++p) { const int numcomps = ctx->pagecomps[p].size(); int unique_in_doc = 0; std::map symcount; for (std::vector::const_iterator i = ctx->pagecomps[p].begin(); i != ctx->pagecomps[p].end(); ++i) { const int sym = (int) ctx->classer->naclass->array[*i]; symcount[sym]++; if (usecount[sym] == 1) unique_in_doc++; } int unique_this_page = 0; for (std::map::const_iterator i = symcount.begin(); i != symcount.end(); ++i) { if (i->second == 1) unique_this_page++; } fprintf(stderr, "Page %d %d/%d/%d\n", p, numcomps, unique_this_page, unique_in_doc); } #endif #ifdef DUMP_ALL_SYMBOLS char filenamebuf[128]; for (int i = 0; i < ctx->classer->pixat->n; ++i) { sprintf(filenamebuf, "sym-%d.png", i); pixWrite(filenamebuf, ctx->classer->pixat->pix[i], IFF_PNG); } #endif fprintf(stderr, "JBIG2 compression complete. pages:%d symbols:%d log2:%d\n", ctx->classer->npages, ctx->classer->pixat->n, log2up(ctx->classer->pixat->n)); jbGetLLCorners(ctx->classer); struct jbig2enc_ctx ectx; jbig2enc_init(&ectx); struct jbig2_file_header header; if (ctx->full_headers) { memset(&header, 0, sizeof(header)); header.n_pages = htonl(ctx->classer->npages); header.organisation_type = 1; memcpy(&header.id, JBIG2_FILE_MAGIC, 8); } Segment seg; struct jbig2_symbol_dict symtab; memset(&symtab, 0, sizeof(symtab)); jbig2enc_symboltable (&ectx, ctx->avg_templates ? ctx->avg_templates : ctx->classer->pixat, &multiuse_symbols, &ctx->symmap, ctx->avg_templates == NULL); const int symdatasize = jbig2enc_datasize(&ectx); symtab.a1x = 3; symtab.a1y = -1; symtab.a2x = -3; symtab.a2y = -1; symtab.a3x = 2; symtab.a3y = -2; symtab.a4x = -2; symtab.a4y = -2; symtab.exsyms = symtab.newsyms = htonl(multiuse_symbols.size()); ctx->symtab_segment = ctx->segnum; seg.number = ctx->segnum; ctx->segnum++; seg.type = segment_symbol_table; seg.len = sizeof(symtab) + symdatasize; seg.page = 0; seg.retain_bits = 1; u8 *const ret = (u8 *) malloc((ctx->full_headers ? sizeof(header) : 0) + seg.size() + sizeof(symtab) + symdatasize); int offset = 0; if (ctx->full_headers) { F(header); } SEGMENT(seg); F(symtab); jbig2enc_tobuffer(&ectx, ret + offset); jbig2enc_dealloc(&ectx); offset += symdatasize; *length = offset; return ret; } // see comments in .h file uint8_t * jbig2_produce_page(struct jbig2ctx *ctx, int page_no, int xres, int yres, int *const length) { const bool last_page = page_no == ctx->classer->npages; const bool include_trailer = last_page && ctx->full_headers; struct jbig2enc_ctx ectx; jbig2enc_init(&ectx); Segment seg, symseg; Segment endseg, trailerseg; struct jbig2_page_info pageinfo; memset(&pageinfo, 0, sizeof(pageinfo)); struct jbig2_text_region textreg; memset(&textreg, 0, sizeof(textreg)); struct jbig2_text_region_syminsts textreg_syminsts; memset(&textreg_syminsts, 0, sizeof(textreg_syminsts)); struct jbig2_text_region_atflags textreg_atflags; memset(&textreg_atflags, 0, sizeof(textreg_atflags)); Segment segr; // page information segment seg.number = ctx->segnum; ctx->segnum++; seg.type = segment_page_information; seg.page = ctx->pdf_page_numbering ? 1 : 1 + page_no; seg.len = sizeof(struct jbig2_page_info); pageinfo.width = htonl(ctx->page_width[page_no]); pageinfo.height = htonl(ctx->page_height[page_no]); pageinfo.xres = htonl(xres == -1 ? ctx->xres : xres); pageinfo.yres = htonl(yres == -1 ? ctx->yres : yres); pageinfo.is_lossless = ctx->refinement; std::map second_symbol_map; // If we have single-use symbols on this page we make a new symbol table // containing just them. const bool extrasymtab = ctx->single_use_symbols[page_no].size() > 0; struct jbig2enc_ctx extrasymtab_ctx; struct jbig2_symbol_dict symtab; memset(&symtab, 0, sizeof(symtab)); if (extrasymtab) { jbig2enc_init(&extrasymtab_ctx); symseg.number = ctx->segnum++; symseg.type = segment_symbol_table; symseg.page = ctx->pdf_page_numbering ? 1 : 1 + page_no; jbig2enc_symboltable (&extrasymtab_ctx, ctx->avg_templates ? ctx->avg_templates : ctx->classer->pixat, &ctx->single_use_symbols[page_no], &second_symbol_map, ctx->avg_templates == NULL); symtab.a1x = 3; symtab.a1y = -1; symtab.a2x = -3; symtab.a2y = -1; symtab.a3x = 2; symtab.a3y = -2; symtab.a4x = -2; symtab.a4y = -2; symtab.exsyms = symtab.newsyms = htonl(ctx->single_use_symbols[page_no].size()); symseg.len = jbig2enc_datasize(&extrasymtab_ctx) + sizeof(symtab); } const int numsyms = ctx->num_global_symbols + ctx->single_use_symbols[page_no].size(); //BOXA *const boxes = ctx->refinement ? ctx->boxes[page_no] : NULL; int baseindex = ctx->refinement ? ctx->baseindexes[page_no] : 0; jbig2enc_textregion(&ectx, ctx->symmap, second_symbol_map, ctx->pagecomps[page_no], ctx->classer->ptall, ctx->avg_templates ? ctx->avg_templates : ctx->classer->pixat, ctx->classer->naclass, 1, log2up(numsyms), //ctx->refinement ? ctx->comps[page_no] : NULL, NULL, /* boxes */ NULL, baseindex, ctx->refine_level, ctx->avg_templates == NULL); const int textdatasize = jbig2enc_datasize(&ectx); textreg.width = htonl(ctx->page_width[page_no]); textreg.height = htonl(ctx->page_height[page_no]); textreg.logsbstrips = 0; textreg.sbrefine = ctx->refinement; // refcorner = 0 -> bot left textreg_syminsts.sbnuminstances = htonl(ctx->pagecomps[page_no].size()); textreg_atflags.a1x = -1; textreg_atflags.a1y = -1; textreg_atflags.a2x = -1; textreg_atflags.a2y = -1; segr.number = ctx->segnum; ctx->segnum++; segr.type = segment_imm_text_region; segr.referred_to.push_back(ctx->symtab_segment); if (extrasymtab) segr.referred_to.push_back(symseg.number); if (ctx->refinement) { segr.len = sizeof(textreg) + sizeof(textreg_syminsts) + sizeof(textreg_atflags) + textdatasize; } else { segr.len = sizeof(textreg) + sizeof(textreg_syminsts) + textdatasize; } segr.retain_bits = 2; segr.page = ctx->pdf_page_numbering ? 1 : 1 + page_no; const int extrasymtab_size = extrasymtab ? jbig2enc_datasize(&extrasymtab_ctx) : 0; if (ctx->full_headers) { endseg.number = ctx->segnum; ctx->segnum++; endseg.type = segment_end_of_page; endseg.page = ctx->pdf_page_numbering ? 1 : 1 + page_no; } if (include_trailer) { trailerseg.number = ctx->segnum; ctx->segnum++; trailerseg.type = segment_end_of_file; trailerseg.page = 0; } const int totalsize = seg.size() + sizeof(pageinfo) + (extrasymtab ? (extrasymtab_size + symseg.size() + sizeof(symtab)) : 0) + segr.size() + sizeof(textreg) + sizeof(textreg_syminsts) + (ctx->refinement ? sizeof(textreg_atflags) : 0) + textdatasize + (ctx->full_headers ? endseg.size() : 0) + (include_trailer ? trailerseg.size() : 0); u8 *ret = (u8 *) malloc(totalsize); int offset = 0; SEGMENT(seg); F(pageinfo); if (extrasymtab) { SEGMENT(symseg); F(symtab); jbig2enc_tobuffer(&extrasymtab_ctx, ret + offset); offset += extrasymtab_size; } SEGMENT(segr); F(textreg); if (ctx->refinement) { F(textreg_atflags); } F(textreg_syminsts); jbig2enc_tobuffer(&ectx, ret + offset); offset += textdatasize; if (ctx->full_headers) { SEGMENT(endseg); } if (include_trailer) { SEGMENT(trailerseg); } if (totalsize != offset) abort(); jbig2enc_dealloc(&ectx); if (extrasymtab) jbig2enc_dealloc(&extrasymtab_ctx); *length = offset; return ret; } #undef F #undef G // see comments in .h file u8 * jbig2_encode_generic(struct Pix *const bw, const bool full_headers, const int xres, const int yres, const bool duplicate_line_removal, int *const length) { int segnum = 0; if (!bw) return NULL; pixSetPadBits(bw, 0); struct jbig2_file_header header; if (full_headers) { memset(&header, 0, sizeof(header)); header.n_pages = htonl(1); header.organisation_type = 1; memcpy(&header.id, JBIG2_FILE_MAGIC, 8); } // setup compression struct jbig2enc_ctx ctx; jbig2enc_init(&ctx); Segment seg, seg2, endseg; jbig2_page_info pageinfo; memset(&pageinfo, 0, sizeof(pageinfo)); jbig2_generic_region genreg; memset(&genreg, 0, sizeof(genreg)); seg.number = segnum; segnum++; seg.type = segment_page_information; seg.page = 1; seg.len = sizeof(struct jbig2_page_info); pageinfo.width = htonl(bw->w); pageinfo.height = htonl(bw->h); pageinfo.xres = htonl(xres ? xres : bw->xres); pageinfo.yres = htonl(yres ? yres : bw->yres); pageinfo.is_lossless = 1; #ifdef SURPRISE_MAP dprintf(3, "P5\n%d %d 255\n", bw->w, bw->h); #endif jbig2enc_bitimage(&ctx, (u8 *) bw->data, bw->w, bw->h, duplicate_line_removal); jbig2enc_final(&ctx); const int datasize = jbig2enc_datasize(&ctx); seg2.number = segnum; segnum++; seg2.type = segment_imm_generic_region; seg2.page = 1; seg2.len = sizeof(genreg) + datasize; endseg.number = segnum; segnum++; endseg.page = 1; genreg.width = htonl(bw->w); genreg.height = htonl(bw->h); if (duplicate_line_removal) { genreg.tpgdon = true; } genreg.a1x = 3; genreg.a1y = -1; genreg.a2x = -3; genreg.a2y = -1; genreg.a3x = 2; genreg.a3y = -2; genreg.a4x = -2; genreg.a4y = -2; const int totalsize = seg.size() + sizeof(pageinfo) + seg2.size() + sizeof(genreg) + datasize + (full_headers ? (sizeof(header) + 2*endseg.size()) : 0); u8 *const ret = (u8 *) malloc(totalsize); int offset = 0; #define F(x) memcpy(ret + offset, &x, sizeof(x)) ; offset += sizeof(x) if (full_headers) { F(header); } SEGMENT(seg); F(pageinfo); SEGMENT(seg2); F(genreg); jbig2enc_tobuffer(&ctx, ret + offset); offset += datasize; if (full_headers) { endseg.type = segment_end_of_page; SEGMENT(endseg); endseg.type = segment_end_of_file; SEGMENT(endseg); } if (totalsize != offset) abort(); jbig2enc_dealloc(&ctx); *length = offset; return ret; }