Skip to content
Does UniPDF support bitonal encoding (CCITT/JBIG2)?

Does UniPDF support bitonal encoding (CCITT/JBIG2)?

Yes, both codecs, in both directions. core.CCITTFaxEncoder and core.JBIG2Encoder decode images out of existing files and encode new ones, and both were written by UniDoc for this library rather than wrapped from elsewhere.

The one restriction is on the JBIG2 encoding side: only generic region coding is implemented. JB2SymbolCorrelation and JB2SymbolRankHaus are declared but rejected at runtime with “not implemented yet”, so Compression stays at the default JB2Generic. Decoding has no such limit and handles the symbol dictionary segments a JBIG2 file from another producer will contain. CCITT covers Group 3 one-dimensional, Group 3 two-dimensional and Group 4, selected by the K field the same way the PDF filter parameter works: 0 for G3 1-D, positive for G3 2-D, negative for G4.

Compressing an image into a PDF

Both encoders take one bit per pixel, one component, so the image has to be reduced to black and white first. ConvertToBinary does that in place.

c := creator.New()

img, err := c.NewImageFromFile("scan.jpg")
if err != nil {
    return err
}

// JBIG2 and CCITT both require a bitonal image.
if err := img.ConvertToBinary(); err != nil {
    return err
}

encoder := core.NewJBIG2Encoder()
encoder.DefaultPageSettings.DuplicatedLinesRemoval = true
img.SetEncoder(encoder)

if err := c.Draw(img); err != nil {
    return err
}
return c.WriteToFile("scan.pdf")

core.NewCCITTFaxEncoder() substitutes directly; it defaults to Group 3 1-D with 1728 columns, and the width and height are filled in from the image when the encoder is attached, so Columns and Rows do not need setting by hand.

DuplicatedLinesRemoval makes the JBIG2 encoder store a repeated scan line once, which is worth having on documents with large blank areas. The rest of JBIG2EncoderSettings covers the initial pixel value (DefaultPixelValue), the resolution recorded in the segment (ResolutionX, ResolutionY) and FileMode.

Going through model instead of the creator, the encoder is the third argument to model.NewXObjectImageFromImage.

Standalone JBIG2 files

FileMode adds the JBIG2 file header, which is what turns the output into a .jb2 file rather than the headerless stream a PDF embeds. Pages are added one at a time and Encode closes the document:

jb2Img, err := core.GoImageToJBIG2(goImg, core.JB2ImageAutoThreshold)
if err != nil {
    return err
}

encoder := &core.JBIG2Encoder{
    DefaultPageSettings: core.JBIG2EncoderSettings{
        FileMode:               true,
        DuplicatedLinesRemoval: true,
    },
}
if err := encoder.AddPageImage(jb2Img, nil); err != nil {
    return err
}

data, err := encoder.Encode()

GoImageToJBIG2 is the thresholding step for a grayscale or color source. JB2ImageAutoThreshold picks the cut from the image histogram using the Triangle method; a value between 0 and 1 sets it explicitly.

Reading the other way, DecodeImages returns every page of a JBIG2 document as a Go image. Where the file keeps its symbol dictionary in a separate globals stream, decode that with DecodeGlobals and pass the result in the encoder’s Globals field. Inside a PDF this is handled for you: the decoder picks up JBIG2Globals from the stream’s DecodeParms.

Which one to use

JBIG2 produces substantially smaller files than CCITT on scanned text, which is why it exists, at the cost of a much more complex format and slower encoding. CCITT G4 is the safer choice for fax-derived content and for consumers that predate JBIG2 support. Both encoders are lossless in the modes above; the loss, where there is any, happened in the threshold that turned a grayscale scan into black and white.

The optimizer leaves images already using either filter alone when it recompresses a document, on the grounds that they are about as small as it can make them.

git clone https://github.com/unidoc/unipdf-examples.git
cd unipdf-examples/jbig2
go run jbig2_compress_image_in_pdf.go output.pdf img1.jpg

If this is your first time using UniPDF, follow the getting started guide to create an API key and set up your development environment.

The same directory holds examples for writing a standalone .jb2 file and for decoding one with and without globals. There is more on the format itself in our blog post on JBIG2 in Golang.

Last updated on