LoboEvolution/LoboEvolution

View on GitHub
LoboPDF/src/main/java/org/jpedal/jbig2/image/JBIG2Bitmap.java

Summary

Maintainability
F
2 wks
Test Coverage
/*
 * MIT License
 *
 * Copyright (c) 2014 - 2024 LoboEvolution
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * Contact info: ivan.difrancesco@yahoo.it
 */
package org.jpedal.jbig2.image;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.jpedal.jbig2.JBIG2Exception;
import org.jpedal.jbig2.decoders.*;
import org.jpedal.jbig2.util.BinaryOperation;

import java.awt.image.*;
import java.io.IOException;

/**
 * <p>JBIG2Bitmap class.</p>
 */
@Slf4j
@Data
public final class JBIG2Bitmap {
    private final int width;
    private final int line;
    private final ArithmeticDecoder arithmeticDecoder;
    private final HuffmanDecoder huffmanDecoder;
    private final MMRDecoder mmrDecoder;

    //private BitSet data;

    //private static int counter = 0;
    public FastBitSet data;
    private int height;
    private int bitmapNumber;

    /**
     * <p>Constructor for JBIG2Bitmap.</p>
     *
     * @param width             a {@link java.lang.Integer} object.
     * @param height            a {@link java.lang.Integer} object.
     * @param arithmeticDecoder a {@link org.jpedal.jbig2.decoders.ArithmeticDecoder} object.
     * @param huffmanDecoder    a {@link org.jpedal.jbig2.decoders.HuffmanDecoder} object.
     * @param mmrDecoder        a {@link org.jpedal.jbig2.decoders.MMRDecoder} object.
     */
    public JBIG2Bitmap(final int width, final int height, final ArithmeticDecoder arithmeticDecoder, final HuffmanDecoder huffmanDecoder, final MMRDecoder mmrDecoder) {
        this.width = width;
        this.height = height;
        this.arithmeticDecoder = arithmeticDecoder;
        this.huffmanDecoder = huffmanDecoder;
        this.mmrDecoder = mmrDecoder;

        this.line = (width + 7) >> 3;

        this.data = new FastBitSet(width * height);
    }

    /**
     * <p>readBitmap.</p>
     *
     * @param useMMR                             a boolean.
     * @param template                           a {@link java.lang.Integer} object.
     * @param typicalPredictionGenericDecodingOn a boolean.
     * @param useSkip                            a boolean.
     * @param skipBitmap                         a {@link org.jpedal.jbig2.image.JBIG2Bitmap} object.
     * @param adaptiveTemplateX                  an array of {@link short} objects.
     * @param adaptiveTemplateY                  an array of {@link short} objects.
     * @param mmrDataLength                      a {@link java.lang.Integer} object.
     * @throws java.io.IOException             if any.
     * @throws org.jpedal.jbig2.JBIG2Exception if any.
     */
    public void readBitmap(final boolean useMMR, final int template, final boolean typicalPredictionGenericDecodingOn, final boolean useSkip, final JBIG2Bitmap skipBitmap, final short[] adaptiveTemplateX, final short[] adaptiveTemplateY, final int mmrDataLength) throws IOException, JBIG2Exception {

        if (useMMR) {

            //MMRDecoder mmrDecoder = MMRDecoder.getInstance();
            mmrDecoder.reset();

            final int[] referenceLine = new int[width + 2];
            final int[] codingLine = new int[width + 2];
            codingLine[0] = codingLine[1] = width;

            for (int row = 0; row < height; row++) {

                int i = 0;
                for (; codingLine[i] < width; i++) {
                    referenceLine[i] = codingLine[i];
                }
                referenceLine[i] = referenceLine[i + 1] = width;

                int referenceI = 0;
                int codingI = 0;
                int a0 = 0;

                do {
                    int code1 = mmrDecoder.get2DCode(), code2, code3;

                    switch (code1) {
                        case MMRDecoder.twoDimensionalPass:
                            if (referenceLine[referenceI] < width) {
                                a0 = referenceLine[referenceI + 1];
                                referenceI += 2;
                            }
                            break;
                        case MMRDecoder.twoDimensionalHorizontal:
                            if ((codingI & 1) != 0) {
                                code1 = 0;
                                do {
                                    code1 += code3 = mmrDecoder.getBlackCode();
                                } while (code3 >= 64);

                                code2 = 0;
                                do {
                                    code2 += code3 = mmrDecoder.getWhiteCode();
                                } while (code3 >= 64);
                            } else {
                                code1 = 0;
                                do {
                                    code1 += code3 = mmrDecoder.getWhiteCode();
                                } while (code3 >= 64);

                                code2 = 0;
                                do {
                                    code2 += code3 = mmrDecoder.getBlackCode();
                                } while (code3 >= 64);

                            }
                            if (code1 > 0 || code2 > 0) {
                                a0 = codingLine[codingI++] = a0 + code1;
                                a0 = codingLine[codingI++] = a0 + code2;

                                while (referenceLine[referenceI] <= a0 && referenceLine[referenceI] < width) {
                                    referenceI += 2;
                                }
                            }
                            break;
                        case MMRDecoder.twoDimensionalVertical0:
                            a0 = codingLine[codingI++] = referenceLine[referenceI];
                            if (referenceLine[referenceI] < width) {
                                referenceI++;
                            }

                            break;
                        case MMRDecoder.twoDimensionalVerticalR1:
                            a0 = codingLine[codingI++] = referenceLine[referenceI] + 1;
                            if (referenceLine[referenceI] < width) {
                                referenceI++;
                                while (referenceLine[referenceI] <= a0 && referenceLine[referenceI] < width) {
                                    referenceI += 2;
                                }
                            }

                            break;
                        case MMRDecoder.twoDimensionalVerticalR2:
                            a0 = codingLine[codingI++] = referenceLine[referenceI] + 2;
                            if (referenceLine[referenceI] < width) {
                                referenceI++;
                                while (referenceLine[referenceI] <= a0 && referenceLine[referenceI] < width) {
                                    referenceI += 2;
                                }
                            }

                            break;
                        case MMRDecoder.twoDimensionalVerticalR3:
                            a0 = codingLine[codingI++] = referenceLine[referenceI] + 3;
                            if (referenceLine[referenceI] < width) {
                                referenceI++;
                                while (referenceLine[referenceI] <= a0 && referenceLine[referenceI] < width) {
                                    referenceI += 2;
                                }
                            }

                            break;
                        case MMRDecoder.twoDimensionalVerticalL1:
                            a0 = codingLine[codingI++] = referenceLine[referenceI] - 1;
                            if (referenceI > 0) {
                                referenceI--;
                            } else {
                                referenceI++;
                            }

                            while (referenceLine[referenceI] <= a0 && referenceLine[referenceI] < width) {
                                referenceI += 2;
                            }

                            break;
                        case MMRDecoder.twoDimensionalVerticalL2:
                            a0 = codingLine[codingI++] = referenceLine[referenceI] - 2;
                            if (referenceI > 0) {
                                referenceI--;
                            } else {
                                referenceI++;
                            }

                            while (referenceLine[referenceI] <= a0 && referenceLine[referenceI] < width) {
                                referenceI += 2;
                            }

                            break;
                        case MMRDecoder.twoDimensionalVerticalL3:
                            a0 = codingLine[codingI++] = referenceLine[referenceI] - 3;
                            if (referenceI > 0) {
                                referenceI--;
                            } else {
                                referenceI++;
                            }

                            while (referenceLine[referenceI] <= a0 && referenceLine[referenceI] < width) {
                                referenceI += 2;
                            }

                            break;
                        default:
                            if (JBIG2StreamDecoder.debug)
                                log.info("Illegal code in JBIG2 MMR bitmap data");

                            break;
                    }
                } while (a0 < width);

                codingLine[codingI++] = width;

                for (int j = 0; codingLine[j] < width; j += 2) {
                    for (int col = codingLine[j]; col < codingLine[j + 1]; col++) {
                        setPixel(col, row, 1);
                    }
                }
            }

            if (mmrDataLength >= 0) {
                mmrDecoder.skipTo(mmrDataLength);
            } else {
                if (mmrDecoder.get24Bits() != 0x001001) {
                    if (JBIG2StreamDecoder.debug)
                        log.info("Missing EOFB in JBIG2 MMR bitmap data");
                }
            }

        } else {

            //ArithmeticDecoder arithmeticDecoder = ArithmeticDecoder.getInstance();

            final BitmapPointer cxPtr0 = new BitmapPointer(this);
            final BitmapPointer cxPtr1 = new BitmapPointer(this);
            final BitmapPointer atPtr0 = new BitmapPointer(this);
            final BitmapPointer atPtr1 = new BitmapPointer(this);
            final BitmapPointer atPtr2 = new BitmapPointer(this);
            final BitmapPointer atPtr3 = new BitmapPointer(this);

            long ltpCX = 0;
            if (typicalPredictionGenericDecodingOn) {
                switch (template) {
                    case 0:
                        ltpCX = 0x3953;
                        break;
                    case 1:
                        ltpCX = 0x079a;
                        break;
                    case 2:
                        ltpCX = 0x0e3;
                        break;
                    case 3:
                        ltpCX = 0x18a;
                        break;
                    default:
                        break;
                }
            }

            boolean ltp = false;
            long cx, cx0, cx1, cx2;

            for (int row = 0; row < height; row++) {
                if (typicalPredictionGenericDecodingOn) {
                    final int bit = arithmeticDecoder.decodeBit(ltpCX, arithmeticDecoder.genericRegionStats);
                    if (bit != 0) {
                        ltp = !ltp;
                    }

                    if (ltp) {
                        duplicateRow(row, row - 1);
                        continue;
                    }
                }

                int pixel;

                switch (template) {
                    case 0:

                        cxPtr0.setPointer(0, row - 2);
                        cx0 = (cxPtr0.nextPixel() << 1);
                        cx0 |= cxPtr0.nextPixel();
                        //cx0 = (BinaryOperation.bit32ShiftL(cx0, 1)) | cxPtr0.nextPixel();

                        cxPtr1.setPointer(0, row - 1);
                        cx1 = (cxPtr1.nextPixel() << 2);
                        cx1 |= (cxPtr1.nextPixel() << 1);
                        cx1 |= (cxPtr1.nextPixel());

                        //cx1 = (BinaryOperation.bit32ShiftL(cx1, 1)) | cxPtr1.nextPixel();
                        //cx1 = (BinaryOperation.bit32ShiftL(cx1, 1)) | cxPtr1.nextPixel();

                        cx2 = 0;

                        atPtr0.setPointer(adaptiveTemplateX[0], row + adaptiveTemplateY[0]);
                        atPtr1.setPointer(adaptiveTemplateX[1], row + adaptiveTemplateY[1]);
                        atPtr2.setPointer(adaptiveTemplateX[2], row + adaptiveTemplateY[2]);
                        atPtr3.setPointer(adaptiveTemplateX[3], row + adaptiveTemplateY[3]);

                        for (int col = 0; col < width; col++) {

                            cx = (BinaryOperation.bit32ShiftL(cx0, 13)) | (BinaryOperation.bit32ShiftL(cx1, 8)) | (BinaryOperation.bit32ShiftL(cx2, 4)) | (atPtr0.nextPixel() << 3) | (atPtr1.nextPixel() << 2) | (atPtr2.nextPixel() << 1) | atPtr3.nextPixel();

                            if (useSkip && skipBitmap.getPixel(col, row) != 0) {
                                pixel = 0;
                            } else {
                                pixel = arithmeticDecoder.decodeBit(cx, arithmeticDecoder.genericRegionStats);
                                if (pixel != 0) {
                                    data.set(row * width + col);
                                }
                            }

                            cx0 = ((BinaryOperation.bit32ShiftL(cx0, 1)) | cxPtr0.nextPixel()) & 0x07;
                            cx1 = ((BinaryOperation.bit32ShiftL(cx1, 1)) | cxPtr1.nextPixel()) & 0x1f;
                            cx2 = ((BinaryOperation.bit32ShiftL(cx2, 1)) | pixel) & 0x0f;
                        }
                        break;

                    case 1:

                        cxPtr0.setPointer(0, row - 2);
                        cx0 = (cxPtr0.nextPixel() << 2);
                        cx0 |= (cxPtr0.nextPixel() << 1);
                        cx0 |= (cxPtr0.nextPixel());
                    /*cx0 = cxPtr0.nextPixel();
                    cx0 = (BinaryOperation.bit32ShiftL(cx0, 1)) | cxPtr0.nextPixel();
                    cx0 = (BinaryOperation.bit32ShiftL(cx0, 1)) | cxPtr0.nextPixel();*/

                        cxPtr1.setPointer(0, row - 1);
                        cx1 = (cxPtr1.nextPixel() << 2);
                        cx1 |= (cxPtr1.nextPixel() << 1);
                        cx1 |= (cxPtr1.nextPixel());
                    /*cx1 = cxPtr1.nextPixel();
                    cx1 = (BinaryOperation.bit32ShiftL(cx1, 1)) | cxPtr1.nextPixel();
                    cx1 = (BinaryOperation.bit32ShiftL(cx1, 1)) | cxPtr1.nextPixel();*/

                        cx2 = 0;

                        atPtr0.setPointer(adaptiveTemplateX[0], row + adaptiveTemplateY[0]);

                        for (int col = 0; col < width; col++) {

                            cx = (BinaryOperation.bit32ShiftL(cx0, 9)) | (BinaryOperation.bit32ShiftL(cx1, 4)) | (BinaryOperation.bit32ShiftL(cx2, 1)) | atPtr0.nextPixel();

                            if (useSkip && skipBitmap.getPixel(col, row) != 0) {
                                pixel = 0;
                            } else {
                                pixel = arithmeticDecoder.decodeBit(cx, arithmeticDecoder.genericRegionStats);
                                if (pixel != 0) {
                                    data.set(row * width + col);
                                }
                            }

                            cx0 = ((BinaryOperation.bit32ShiftL(cx0, 1)) | cxPtr0.nextPixel()) & 0x0f;
                            cx1 = ((BinaryOperation.bit32ShiftL(cx1, 1)) | cxPtr1.nextPixel()) & 0x1f;
                            cx2 = ((BinaryOperation.bit32ShiftL(cx2, 1)) | pixel) & 0x07;
                        }
                        break;

                    case 2:

                        cxPtr0.setPointer(0, row - 2);
                        cx0 = (cxPtr0.nextPixel() << 1);
                        cx0 |= (cxPtr0.nextPixel());
                    /*cx0 = cxPtr0.nextPixel();
                    cx0 = (BinaryOperation.bit32ShiftL(cx0, 1)) | cxPtr0.nextPixel();
                    */

                        cxPtr1.setPointer(0, row - 1);
                        cx1 = (cxPtr1.nextPixel() << 1);
                        cx1 |= (cxPtr1.nextPixel());
                    /*cx1 = cxPtr1.nextPixel();
                    cx1 = (BinaryOperation.bit32ShiftL(cx1, 1)) | cxPtr1.nextPixel();*/

                        cx2 = 0;

                        atPtr0.setPointer(adaptiveTemplateX[0], row + adaptiveTemplateY[0]);

                        for (int col = 0; col < width; col++) {

                            cx = (BinaryOperation.bit32ShiftL(cx0, 7)) | (BinaryOperation.bit32ShiftL(cx1, 3)) | (BinaryOperation.bit32ShiftL(cx2, 1)) | atPtr0.nextPixel();

                            if (useSkip && skipBitmap.getPixel(col, row) != 0) {
                                pixel = 0;
                            } else {
                                pixel = arithmeticDecoder.decodeBit(cx, arithmeticDecoder.genericRegionStats);
                                if (pixel != 0) {
                                    data.set(row * width + col);
                                }
                            }

                            cx0 = ((BinaryOperation.bit32ShiftL(cx0, 1)) | cxPtr0.nextPixel()) & 0x07;
                            cx1 = ((BinaryOperation.bit32ShiftL(cx1, 1)) | cxPtr1.nextPixel()) & 0x0f;
                            cx2 = ((BinaryOperation.bit32ShiftL(cx2, 1)) | pixel) & 0x03;
                        }
                        break;

                    case 3:

                        cxPtr1.setPointer(0, row - 1);
                        cx1 = (cxPtr1.nextPixel() << 1);
                        cx1 |= (cxPtr1.nextPixel());
                    /*cx1 = cxPtr1.nextPixel();
                    cx1 = (BinaryOperation.bit32ShiftL(cx1, 1)) | cxPtr1.nextPixel();
*/
                        cx2 = 0;

                        atPtr0.setPointer(adaptiveTemplateX[0], row + adaptiveTemplateY[0]);

                        for (int col = 0; col < width; col++) {

                            cx = (BinaryOperation.bit32ShiftL(cx1, 5)) | (BinaryOperation.bit32ShiftL(cx2, 1)) | atPtr0.nextPixel();

                            if (useSkip && skipBitmap.getPixel(col, row) != 0) {
                                pixel = 0;

                            } else {
                                pixel = arithmeticDecoder.decodeBit(cx, arithmeticDecoder.genericRegionStats);
                                if (pixel != 0) {
                                    data.set(row * width + col);
                                }
                            }

                            cx1 = ((BinaryOperation.bit32ShiftL(cx1, 1)) | cxPtr1.nextPixel()) & 0x1f;
                            cx2 = ((BinaryOperation.bit32ShiftL(cx2, 1)) | pixel) & 0x0f;
                        }
                        break;
                    default:
                        break;
                }
            }
        }
    }

    /**
     * <p>readGenericRefinementRegion.</p>
     *
     * @param template                             a {@link java.lang.Integer} object.
     * @param typicalPredictionGenericRefinementOn a boolean.
     * @param referredToBitmap                     a {@link org.jpedal.jbig2.image.JBIG2Bitmap} object.
     * @param referenceDX                          a {@link java.lang.Integer} object.
     * @param referenceDY                          a {@link java.lang.Integer} object.
     * @param adaptiveTemplateX                    an array of {@link short} objects.
     * @param adaptiveTemplateY                    an array of {@link short} objects.
     * @throws java.io.IOException             if any.
     * @throws org.jpedal.jbig2.JBIG2Exception if any.
     */
    public void readGenericRefinementRegion(final int template, final boolean typicalPredictionGenericRefinementOn, final JBIG2Bitmap referredToBitmap, final int referenceDX, final int referenceDY, final short[] adaptiveTemplateX, final short[] adaptiveTemplateY) throws IOException, JBIG2Exception {

        //ArithmeticDecoder arithmeticDecoder = ArithmeticDecoder.getInstance();

        final BitmapPointer cxPtr0;
        final BitmapPointer cxPtr1;
        final BitmapPointer cxPtr2;
        final BitmapPointer cxPtr3;
        final BitmapPointer cxPtr4;
        final BitmapPointer cxPtr5;
        final BitmapPointer cxPtr6;
        final BitmapPointer typicalPredictionGenericRefinementCXPtr0;
        final BitmapPointer typicalPredictionGenericRefinementCXPtr1;
        final BitmapPointer typicalPredictionGenericRefinementCXPtr2;

        final long ltpCX;
        if (template != 0) {
            ltpCX = 0x008;

            cxPtr0 = new BitmapPointer(this);
            cxPtr1 = new BitmapPointer(this);
            cxPtr2 = new BitmapPointer(referredToBitmap);
            cxPtr3 = new BitmapPointer(referredToBitmap);
            cxPtr4 = new BitmapPointer(referredToBitmap);
            cxPtr5 = new BitmapPointer(this);
            cxPtr6 = new BitmapPointer(this);
            typicalPredictionGenericRefinementCXPtr0 = new BitmapPointer(referredToBitmap);
            typicalPredictionGenericRefinementCXPtr1 = new BitmapPointer(referredToBitmap);
            typicalPredictionGenericRefinementCXPtr2 = new BitmapPointer(referredToBitmap);
        } else {
            ltpCX = 0x0010;

            cxPtr0 = new BitmapPointer(this);
            cxPtr1 = new BitmapPointer(this);
            cxPtr2 = new BitmapPointer(referredToBitmap);
            cxPtr3 = new BitmapPointer(referredToBitmap);
            cxPtr4 = new BitmapPointer(referredToBitmap);
            cxPtr5 = new BitmapPointer(this);
            cxPtr6 = new BitmapPointer(referredToBitmap);
            typicalPredictionGenericRefinementCXPtr0 = new BitmapPointer(referredToBitmap);
            typicalPredictionGenericRefinementCXPtr1 = new BitmapPointer(referredToBitmap);
            typicalPredictionGenericRefinementCXPtr2 = new BitmapPointer(referredToBitmap);
        }

        long cx, cx0, cx2, cx3, cx4;
        long typicalPredictionGenericRefinementCX0, typicalPredictionGenericRefinementCX1, typicalPredictionGenericRefinementCX2;
        boolean ltp = false;

        for (int row = 0; row < height; row++) {

            if (template != 0) {

                cxPtr0.setPointer(0, row - 1);
                cx0 = cxPtr0.nextPixel();

                cxPtr1.setPointer(-1, row);

                cxPtr2.setPointer(-referenceDX, row - 1 - referenceDY);

                cxPtr3.setPointer(-1 - referenceDX, row - referenceDY);
                cx3 = cxPtr3.nextPixel();
                cx3 = (BinaryOperation.bit32ShiftL(cx3, 1)) | cxPtr3.nextPixel();

                cxPtr4.setPointer(-referenceDX, row + 1 - referenceDY);
                cx4 = cxPtr4.nextPixel();

                typicalPredictionGenericRefinementCX0 = typicalPredictionGenericRefinementCX1 = typicalPredictionGenericRefinementCX2 = 0;

                if (typicalPredictionGenericRefinementOn) {
                    typicalPredictionGenericRefinementCXPtr0.setPointer(-1 - referenceDX, row - 1 - referenceDY);
                    typicalPredictionGenericRefinementCX0 = typicalPredictionGenericRefinementCXPtr0.nextPixel();
                    typicalPredictionGenericRefinementCX0 = (BinaryOperation.bit32ShiftL(typicalPredictionGenericRefinementCX0, 1)) | typicalPredictionGenericRefinementCXPtr0.nextPixel();
                    typicalPredictionGenericRefinementCX0 = (BinaryOperation.bit32ShiftL(typicalPredictionGenericRefinementCX0, 1)) | typicalPredictionGenericRefinementCXPtr0.nextPixel();

                    typicalPredictionGenericRefinementCXPtr1.setPointer(-1 - referenceDX, row - referenceDY);
                    typicalPredictionGenericRefinementCX1 = typicalPredictionGenericRefinementCXPtr1.nextPixel();
                    typicalPredictionGenericRefinementCX1 = (BinaryOperation.bit32ShiftL(typicalPredictionGenericRefinementCX1, 1)) | typicalPredictionGenericRefinementCXPtr1.nextPixel();
                    typicalPredictionGenericRefinementCX1 = (BinaryOperation.bit32ShiftL(typicalPredictionGenericRefinementCX1, 1)) | typicalPredictionGenericRefinementCXPtr1.nextPixel();

                    typicalPredictionGenericRefinementCXPtr2.setPointer(-1 - referenceDX, row + 1 - referenceDY);
                    typicalPredictionGenericRefinementCX2 = typicalPredictionGenericRefinementCXPtr2.nextPixel();
                    typicalPredictionGenericRefinementCX2 = (BinaryOperation.bit32ShiftL(typicalPredictionGenericRefinementCX2, 1)) | typicalPredictionGenericRefinementCXPtr2.nextPixel();
                    typicalPredictionGenericRefinementCX2 = (BinaryOperation.bit32ShiftL(typicalPredictionGenericRefinementCX2, 1)) | typicalPredictionGenericRefinementCXPtr2.nextPixel();
                }

                for (int col = 0; col < width; col++) {

                    cx0 = ((BinaryOperation.bit32ShiftL(cx0, 1)) | cxPtr0.nextPixel()) & 7;
                    cx3 = ((BinaryOperation.bit32ShiftL(cx3, 1)) | cxPtr3.nextPixel()) & 7;
                    cx4 = ((BinaryOperation.bit32ShiftL(cx4, 1)) | cxPtr4.nextPixel()) & 3;

                    if (typicalPredictionGenericRefinementOn) {
                        typicalPredictionGenericRefinementCX0 = ((BinaryOperation.bit32ShiftL(typicalPredictionGenericRefinementCX0, 1)) | typicalPredictionGenericRefinementCXPtr0.nextPixel()) & 7;
                        typicalPredictionGenericRefinementCX1 = ((BinaryOperation.bit32ShiftL(typicalPredictionGenericRefinementCX1, 1)) | typicalPredictionGenericRefinementCXPtr1.nextPixel()) & 7;
                        typicalPredictionGenericRefinementCX2 = ((BinaryOperation.bit32ShiftL(typicalPredictionGenericRefinementCX2, 1)) | typicalPredictionGenericRefinementCXPtr2.nextPixel()) & 7;

                        final int decodeBit = arithmeticDecoder.decodeBit(ltpCX, arithmeticDecoder.refinementRegionStats);
                        if (decodeBit != 0) {
                            ltp = !ltp;
                        }
                        if (typicalPredictionGenericRefinementCX0 == 0 && typicalPredictionGenericRefinementCX1 == 0 && typicalPredictionGenericRefinementCX2 == 0) {
                            setPixel(col, row, 0);
                            continue;
                        } else if (typicalPredictionGenericRefinementCX0 == 7 && typicalPredictionGenericRefinementCX1 == 7 && typicalPredictionGenericRefinementCX2 == 7) {
                            setPixel(col, row, 1);
                            continue;
                        }
                    }

                    cx = (BinaryOperation.bit32ShiftL(cx0, 7)) | (cxPtr1.nextPixel() << 6) | (cxPtr2.nextPixel() << 5) | (BinaryOperation.bit32ShiftL(cx3, 2)) | cx4;

                    final int pixel = arithmeticDecoder.decodeBit(cx, arithmeticDecoder.refinementRegionStats);
                    if (pixel == 1) {
                        data.set(row * width + col);
                    }
                }

            } else {

                cxPtr0.setPointer(0, row - 1);
                cx0 = cxPtr0.nextPixel();

                cxPtr1.setPointer(-1, row);

                cxPtr2.setPointer(-referenceDX, row - 1 - referenceDY);
                cx2 = cxPtr2.nextPixel();

                cxPtr3.setPointer(-1 - referenceDX, row - referenceDY);
                cx3 = cxPtr3.nextPixel();
                cx3 = (BinaryOperation.bit32ShiftL(cx3, 1)) | cxPtr3.nextPixel();

                cxPtr4.setPointer(-1 - referenceDX, row + 1 - referenceDY);
                cx4 = cxPtr4.nextPixel();
                cx4 = (BinaryOperation.bit32ShiftL(cx4, 1)) | cxPtr4.nextPixel();

                cxPtr5.setPointer(adaptiveTemplateX[0], row + adaptiveTemplateY[0]);

                cxPtr6.setPointer(adaptiveTemplateX[1] - referenceDX, row + adaptiveTemplateY[1] - referenceDY);

                typicalPredictionGenericRefinementCX0 = typicalPredictionGenericRefinementCX1 = typicalPredictionGenericRefinementCX2 = 0;
                if (typicalPredictionGenericRefinementOn) {
                    typicalPredictionGenericRefinementCXPtr0.setPointer(-1 - referenceDX, row - 1 - referenceDY);
                    typicalPredictionGenericRefinementCX0 = typicalPredictionGenericRefinementCXPtr0.nextPixel();
                    typicalPredictionGenericRefinementCX0 = (BinaryOperation.bit32ShiftL(typicalPredictionGenericRefinementCX0, 1)) | typicalPredictionGenericRefinementCXPtr0.nextPixel();
                    typicalPredictionGenericRefinementCX0 = (BinaryOperation.bit32ShiftL(typicalPredictionGenericRefinementCX0, 1)) | typicalPredictionGenericRefinementCXPtr0.nextPixel();

                    typicalPredictionGenericRefinementCXPtr1.setPointer(-1 - referenceDX, row - referenceDY);
                    typicalPredictionGenericRefinementCX1 = typicalPredictionGenericRefinementCXPtr1.nextPixel();
                    typicalPredictionGenericRefinementCX1 = (BinaryOperation.bit32ShiftL(typicalPredictionGenericRefinementCX1, 1)) | typicalPredictionGenericRefinementCXPtr1.nextPixel();
                    typicalPredictionGenericRefinementCX1 = (BinaryOperation.bit32ShiftL(typicalPredictionGenericRefinementCX1, 1)) | typicalPredictionGenericRefinementCXPtr1.nextPixel();

                    typicalPredictionGenericRefinementCXPtr2.setPointer(-1 - referenceDX, row + 1 - referenceDY);
                    typicalPredictionGenericRefinementCX2 = typicalPredictionGenericRefinementCXPtr2.nextPixel();
                    typicalPredictionGenericRefinementCX2 = (BinaryOperation.bit32ShiftL(typicalPredictionGenericRefinementCX2, 1)) | typicalPredictionGenericRefinementCXPtr2.nextPixel();
                    typicalPredictionGenericRefinementCX2 = (BinaryOperation.bit32ShiftL(typicalPredictionGenericRefinementCX2, 1)) | typicalPredictionGenericRefinementCXPtr2.nextPixel();
                }

                for (int col = 0; col < width; col++) {

                    cx0 = ((BinaryOperation.bit32ShiftL(cx0, 1)) | cxPtr0.nextPixel()) & 3;
                    cx2 = ((BinaryOperation.bit32ShiftL(cx2, 1)) | cxPtr2.nextPixel()) & 3;
                    cx3 = ((BinaryOperation.bit32ShiftL(cx3, 1)) | cxPtr3.nextPixel()) & 7;
                    cx4 = ((BinaryOperation.bit32ShiftL(cx4, 1)) | cxPtr4.nextPixel()) & 7;

                    if (typicalPredictionGenericRefinementOn) {
                        typicalPredictionGenericRefinementCX0 = ((BinaryOperation.bit32ShiftL(typicalPredictionGenericRefinementCX0, 1)) | typicalPredictionGenericRefinementCXPtr0.nextPixel()) & 7;
                        typicalPredictionGenericRefinementCX1 = ((BinaryOperation.bit32ShiftL(typicalPredictionGenericRefinementCX1, 1)) | typicalPredictionGenericRefinementCXPtr1.nextPixel()) & 7;
                        typicalPredictionGenericRefinementCX2 = ((BinaryOperation.bit32ShiftL(typicalPredictionGenericRefinementCX2, 1)) | typicalPredictionGenericRefinementCXPtr2.nextPixel()) & 7;

                        final int decodeBit = arithmeticDecoder.decodeBit(ltpCX, arithmeticDecoder.refinementRegionStats);
                        if (decodeBit == 1) {
                            ltp = !ltp;
                        }
                        if (typicalPredictionGenericRefinementCX0 == 0 && typicalPredictionGenericRefinementCX1 == 0 && typicalPredictionGenericRefinementCX2 == 0) {
                            setPixel(col, row, 0);
                            continue;
                        } else if (typicalPredictionGenericRefinementCX0 == 7 && typicalPredictionGenericRefinementCX1 == 7 && typicalPredictionGenericRefinementCX2 == 7) {
                            setPixel(col, row, 1);
                            continue;
                        }
                    }

                    cx = (BinaryOperation.bit32ShiftL(cx0, 11)) | (cxPtr1.nextPixel() << 10) | (BinaryOperation.bit32ShiftL(cx2, 8)) | (BinaryOperation.bit32ShiftL(cx3, 5)) | (BinaryOperation.bit32ShiftL(cx4, 2)) | (cxPtr5.nextPixel() << 1) | cxPtr6.nextPixel();

                    final int pixel = arithmeticDecoder.decodeBit(cx, arithmeticDecoder.refinementRegionStats);
                    if (pixel == 1) {
                        setPixel(col, row, 1);
                    }
                }
            }
        }
    }

    /**
     * <p>readTextRegion.</p>
     *
     * @param huffman                       a boolean.
     * @param symbolRefine                  a boolean.
     * @param noOfSymbolInstances           a {@link java.lang.Integer} object.
     * @param logStrips                     a {@link java.lang.Integer} object.
     * @param noOfSymbols                   a {@link java.lang.Integer} object.
     * @param symbolCodeTable               an array of {@link int} objects.
     * @param symbolCodeLength              a {@link java.lang.Integer} object.
     * @param symbols                       an array of {@link org.jpedal.jbig2.image.JBIG2Bitmap} objects.
     * @param defaultPixel                  a {@link java.lang.Integer} object.
     * @param combinationOperator           a {@link java.lang.Integer} object.
     * @param transposed                    a boolean.
     * @param referenceCorner               a {@link java.lang.Integer} object.
     * @param sOffset                       a {@link java.lang.Integer} object.
     * @param huffmanFSTable                an array of {@link int} objects.
     * @param huffmanDSTable                an array of {@link int} objects.
     * @param huffmanDTTable                an array of {@link int} objects.
     * @param huffmanRDWTable               an array of {@link int} objects.
     * @param huffmanRDHTable               an array of {@link int} objects.
     * @param huffmanRDXTable               an array of {@link int} objects.
     * @param huffmanRDYTable               an array of {@link int} objects.
     * @param huffmanRSizeTable             an array of {@link int} objects.
     * @param template                      a {@link java.lang.Integer} object.
     * @param symbolRegionAdaptiveTemplateX an array of {@link short} objects.
     * @param symbolRegionAdaptiveTemplateY an array of {@link short} objects.
     * @param decoder                       a {@link org.jpedal.jbig2.decoders.JBIG2StreamDecoder} object.
     * @throws org.jpedal.jbig2.JBIG2Exception if any.
     * @throws java.io.IOException             if any.
     */
    public void readTextRegion(final boolean huffman, final boolean symbolRefine, final int noOfSymbolInstances, final int logStrips, final int noOfSymbols, final int[][] symbolCodeTable, final int symbolCodeLength, final JBIG2Bitmap[] symbols, final int defaultPixel, final int combinationOperator, final boolean transposed, final int referenceCorner, final int sOffset, final int[][] huffmanFSTable, final int[][] huffmanDSTable, final int[][] huffmanDTTable, final int[][] huffmanRDWTable, final int[][] huffmanRDHTable, final int[][] huffmanRDXTable, final int[][] huffmanRDYTable, final int[][] huffmanRSizeTable, final int template, final short[] symbolRegionAdaptiveTemplateX,
                               final short[] symbolRegionAdaptiveTemplateY, final JBIG2StreamDecoder decoder) throws JBIG2Exception, IOException {

        JBIG2Bitmap symbolBitmap;

        final int strips = 1 << logStrips;

        clear(defaultPixel);

        //HuffmanDecoder huffDecoder = HuffmanDecoder.getInstance();
        //ArithmeticDecoder arithmeticDecoder = ArithmeticDecoder.getInstance();

        int t;
        if (huffman) {
            t = huffmanDecoder.decodeInt(huffmanDTTable).intResult();
        } else {
            t = arithmeticDecoder.decodeInt(arithmeticDecoder.iadtStats).intResult();
        }
        t *= -strips;

        int currentInstance = 0;
        int firstS = 0;
        int dt, tt, ds, s;
        while (currentInstance < noOfSymbolInstances) {

            if (huffman) {
                dt = huffmanDecoder.decodeInt(huffmanDTTable).intResult();
            } else {
                dt = arithmeticDecoder.decodeInt(arithmeticDecoder.iadtStats).intResult();
            }
            t += dt * strips;

            if (huffman) {
                ds = huffmanDecoder.decodeInt(huffmanFSTable).intResult();
            } else {
                ds = arithmeticDecoder.decodeInt(arithmeticDecoder.iafsStats).intResult();
            }
            firstS += ds;
            s = firstS;

            while (true) {

                if (strips == 1) {
                    dt = 0;
                } else if (huffman) {
                    dt = decoder.readBits(logStrips);
                } else {
                    dt = arithmeticDecoder.decodeInt(arithmeticDecoder.iaitStats).intResult();
                }
                tt = t + dt;

                final long symbolID;
                if (huffman) {
                    if (symbolCodeTable != null) {
                        symbolID = huffmanDecoder.decodeInt(symbolCodeTable).intResult();
                    } else {
                        symbolID = decoder.readBits(symbolCodeLength);
                    }
                } else {
                    symbolID = arithmeticDecoder.decodeIAID(symbolCodeLength, arithmeticDecoder.iaidStats);
                }

                if (symbolID >= noOfSymbols) {
                    if (JBIG2StreamDecoder.debug)
                        log.info("Invalid symbol number in JBIG2 text region");
                } else {
                    symbolBitmap = null;

                    final int ri;
                    if (symbolRefine) {
                        if (huffman) {
                            ri = decoder.readBit();
                        } else {
                            ri = arithmeticDecoder.decodeInt(arithmeticDecoder.iariStats).intResult();
                        }
                    } else {
                        ri = 0;
                    }
                    if (ri != 0) {

                        final int refinementDeltaWidth;
                        final int refinementDeltaHeight;
                        int refinementDeltaX;
                        int refinementDeltaY;

                        if (huffman) {
                            refinementDeltaWidth = huffmanDecoder.decodeInt(huffmanRDWTable).intResult();
                            refinementDeltaHeight = huffmanDecoder.decodeInt(huffmanRDHTable).intResult();
                            refinementDeltaX = huffmanDecoder.decodeInt(huffmanRDXTable).intResult();
                            refinementDeltaY = huffmanDecoder.decodeInt(huffmanRDYTable).intResult();

                            decoder.consumeRemainingBits();
                            arithmeticDecoder.start();
                        } else {
                            refinementDeltaWidth = arithmeticDecoder.decodeInt(arithmeticDecoder.iardwStats).intResult();
                            refinementDeltaHeight = arithmeticDecoder.decodeInt(arithmeticDecoder.iardhStats).intResult();
                            refinementDeltaX = arithmeticDecoder.decodeInt(arithmeticDecoder.iardxStats).intResult();
                            refinementDeltaY = arithmeticDecoder.decodeInt(arithmeticDecoder.iardyStats).intResult();
                        }
                        refinementDeltaX = ((refinementDeltaWidth >= 0) ? refinementDeltaWidth : refinementDeltaWidth - 1) / 2 + refinementDeltaX;
                        refinementDeltaY = ((refinementDeltaHeight >= 0) ? refinementDeltaHeight : refinementDeltaHeight - 1) / 2 + refinementDeltaY;

                        symbolBitmap = new JBIG2Bitmap(refinementDeltaWidth + symbols[(int) symbolID].width, refinementDeltaHeight + symbols[(int) symbolID].height, arithmeticDecoder, huffmanDecoder, mmrDecoder);

                        symbolBitmap.readGenericRefinementRegion(template, false, symbols[(int) symbolID], refinementDeltaX, refinementDeltaY, symbolRegionAdaptiveTemplateX, symbolRegionAdaptiveTemplateY);

                    } else {
                        symbolBitmap = symbols[(int) symbolID];
                    }

                    final int bitmapWidth = symbolBitmap.width - 1;
                    final int bitmapHeight = symbolBitmap.height - 1;
                    if (transposed) {
                        switch (referenceCorner) {
                            case 0: // bottom left
                            case 1: // top left
                                combine(symbolBitmap, tt, s, combinationOperator);
                                break;
                            case 2: // bottom right
                            case 3: // top right
                                combine(symbolBitmap, (tt - bitmapWidth), s, combinationOperator);
                                break;
                            default:
                                break;
                        }
                        s += bitmapHeight;
                    } else {
                        switch (referenceCorner) {
                            case 0: // bottom left
                            case 2: // bottom right
                                combine(symbolBitmap, s, (tt - bitmapHeight), combinationOperator);
                                break;
                            case 1: // top left
                            case 3: // top right
                                combine(symbolBitmap, s, tt, combinationOperator);
                                break;
                            default:
                                break;
                        }
                        s += bitmapWidth;
                    }
                }

                currentInstance++;

                final DecodeIntResult decodeIntResult;

                if (huffman) {
                    decodeIntResult = huffmanDecoder.decodeInt(huffmanDSTable);
                } else {
                    decodeIntResult = arithmeticDecoder.decodeInt(arithmeticDecoder.iadsStats);
                }

                if (!decodeIntResult.booleanResult()) {
                    break;
                }

                ds = decodeIntResult.intResult();

                s += sOffset + ds;
            }
        }
    }

    /**
     * <p>clear.</p>
     *
     * @param defPixel a {@link java.lang.Integer} object.
     */
    public void clear(final int defPixel) {
        data.setAll(defPixel == 1);
        //data.set(0, data.size(), defPixel == 1);
    }

    /**
     * <p>combine.</p>
     *
     * @param bitmap a {@link org.jpedal.jbig2.image.JBIG2Bitmap} object.
     * @param x      a {@link java.lang.Integer} object.
     * @param y      a {@link java.lang.Integer} object.
     * @param combOp a long.
     */
    public void combine(final JBIG2Bitmap bitmap, final int x, final int y, final long combOp) {
        final int srcWidth = bitmap.width;
        int srcHeight = bitmap.height;

//        int maxRow = y + srcHeight;
//        int maxCol = x + srcWidth;
//
//        for (int row = y; row < maxRow; row++) {
//            for (int col = x; col < maxCol; srcCol += 8, col += 8) {
//
//                byte srcPixelByte = bitmap.getPixelByte(srcCol, srcRow);
//                byte dstPixelByte = getPixelByte(col, row);
//                byte endPixelByte;
//
//                switch ((int) combOp) {
//                case 0: // or
//                    endPixelByte = (byte) (dstPixelByte | srcPixelByte);
//                    break;
//                case 1: // and
//                    endPixelByte = (byte) (dstPixelByte & srcPixelByte);
//                    break;
//                case 2: // xor
//                    endPixelByte = (byte) (dstPixelByte ^ srcPixelByte);
//                    break;
//                case 3: // xnor
//                    endPixelByte = (byte) ~(dstPixelByte ^ srcPixelByte);
//                    break;
//                case 4: // replace
//                default:
//                    endPixelByte = srcPixelByte;
//                    break;
//                }
//                int used = maxCol - col;
//                if (used < 8) {
//                    // mask bits
//                    endPixelByte = (byte) ((endPixelByte & (0xFF >> (8 - used))) | (dstPixelByte & (0xFF << (used))));
//                }
//                setPixelByte(col, row, endPixelByte);
//            }
//
//            srcCol = 0;
//            srcRow++;
        int minWidth = srcWidth;
        if (x + srcWidth > width) {
            //Should not happen but occurs sometimes because there is something wrong with halftone pics
            minWidth = width - x;
        }
        if (y + srcHeight > height) {
            //Should not happen but occurs sometimes because there is something wrong with halftone pics
            srcHeight = height - y;
        }

        int srcIndx = 0;
        int indx = y * width + x;
        if (combOp == 0) {
            if (x == 0 && y == 0 && srcHeight == height && srcWidth == width) {
                for (int i = 0; i < data.w.length; i++) {
                    data.w[i] |= bitmap.data.w[i];
                }
            }
            for (int row = y; row < y + srcHeight; row++) {
                indx = row * width + x;
                data.or(indx, bitmap.data, srcIndx, minWidth);
                /*for (int col = 0; col < minWidth; col++) {
                    if (bitmap.data.get(srcIndx + col)) data.set(indx);
                    //data.set(indx, bitmap.data.get(srcIndx + col) || data.get(indx));
                    indx++;
                }*/
                srcIndx += srcWidth;
            }
        } else if (combOp == 1) {
            if (x == 0 && y == 0 && srcHeight == height && srcWidth == width) {
                for (int i = 0; i < data.w.length; i++) {
                    data.w[i] &= bitmap.data.w[i];
                }
            }
            for (int row = y; row < y + srcHeight; row++) {
                indx = row * width + x;
                for (int col = 0; col < minWidth; col++) {
                    data.set(indx, bitmap.data.get(srcIndx + col) && data.get(indx));
                    indx++;
                }
                srcIndx += srcWidth;
            }
        } else if (combOp == 2) {
            if (x == 0 && y == 0 && srcHeight == height && srcWidth == width) {
                for (int i = 0; i < data.w.length; i++) {
                    data.w[i] ^= bitmap.data.w[i];
                }
            } else {
                for (int row = y; row < y + srcHeight; row++) {
                    indx = row * width + x;
                    for (int col = 0; col < minWidth; col++) {
                        data.set(indx, bitmap.data.get(srcIndx + col) ^ data.get(indx));
                        indx++;
                    }
                    srcIndx += srcWidth;
                }
            }
        } else if (combOp == 3) {
            for (int row = y; row < y + srcHeight; row++) {
                indx = row * width + x;
                for (int col = 0; col < minWidth; col++) {
                    final boolean srcPixel = bitmap.data.get(srcIndx + col);
                    final boolean pixel = data.get(indx);
                    data.set(indx, pixel == srcPixel);
                    indx++;
                }
                srcIndx += srcWidth;
            }
        } else if (combOp == 4) {
            if (x == 0 && y == 0 && srcHeight == height && srcWidth == width) {
                System.arraycopy(bitmap.data.w, 0, data.w, 0, data.w.length);
            } else {
                for (int row = y; row < y + srcHeight; row++) {
                    indx = row * width + x;
                    for (int col = 0; col < minWidth; col++) {
                        data.set(indx, bitmap.data.get(srcIndx + col));
                        srcIndx++;
                        indx++;
                    }
                    srcIndx += srcWidth;
                }
            }
        }
    }
    private void duplicateRow(final int yDest, final int ySrc) {
        for (int i = 0; i < width; i++) {
            setPixel(i, yDest, getPixel(i, ySrc));
        }
    }

    /**
     * <p>Getter for the field <code>data</code>.</p>
     *
     * @param switchPixelColor a boolean.
     * @return an array of {@link byte} objects.
     */
    public byte[] getData(final boolean switchPixelColor) {
        final byte[] bytes = new byte[height * line];

        int count = 0, offset = 0;
        long k = 0;
        for (int row = 0; row < height; row++) {
            for (int col = 0; col < width; col++) {
                if ((count & FastBitSet.mask) == 0) {
                    k = data.w[count >>> FastBitSet.pot];
                }
                //if ((k & (1L << count)) != 0) {
                final int bit = 7 - (offset & 0x7);
                bytes[offset >> 3] |= (byte) (((k >>> count) & 1) << bit);
                //}
                count++;
                offset++;
            }

            offset = (line * 8 * (row + 1));
        }

        if (switchPixelColor) {
            for (int i = 0; i < bytes.length; i++) {
                bytes[i] ^= (byte) 0xff;
            }
        }

        return bytes;
    }

    /**
     * <p>getSlice.</p>
     *
     * @param x      a {@link java.lang.Integer} object.
     * @param y      a {@link java.lang.Integer} object.
     * @param width  a {@link java.lang.Integer} object.
     * @param height a {@link java.lang.Integer} object.
     * @return a {@link org.jpedal.jbig2.image.JBIG2Bitmap} object.
     */
    public JBIG2Bitmap getSlice(final int x, final int y, final int width, final int height) {

        final JBIG2Bitmap slice = new JBIG2Bitmap(width, height, arithmeticDecoder, huffmanDecoder, mmrDecoder);

        int sliceIndx = 0;
        for (int row = y; row < height; row++) {
            int indx = row * this.width + x;
            for (int col = x; col < x + width; col++) {
                if (data.get(indx)) slice.data.set(sliceIndx);
                sliceIndx++;
                indx++;
            }
        }

        return slice;
    }

    private void setPixel(final int col, final int row, final FastBitSet data, final int value) {
        final int index = (row * width) + col;

        data.set(index, value == 1);
    }

    /**
     * <p>setPixel.</p>
     *
     * @param col   a {@link java.lang.Integer} object.
     * @param row   a {@link java.lang.Integer} object.
     * @param value a {@link java.lang.Integer} object.
     */
    public void setPixel(final int col, final int row, final int value) {
        setPixel(col, row, data, value);
    }

    /**
     * <p>getPixel.</p>
     *
     * @param col a {@link java.lang.Integer} object.
     * @param row a {@link java.lang.Integer} object.
     * @return a {@link java.lang.Integer} object.
     */
    public int getPixel(final int col, final int row) {
        return data.get((row * width) + col) ? 1 : 0;
    }

    /**
     * <p>expand.</p>
     *
     * @param newHeight    a {@link java.lang.Integer} object.
     * @param defaultPixel a {@link java.lang.Integer} object.
     */
    public void expand(final int newHeight, final int defaultPixel) {
        final FastBitSet newData = new FastBitSet(newHeight * width);
        for (int row = 0; row < height; row++) {
            for (int col = 0; col < width; col++) {
                setPixel(col, row, newData, getPixel(col, row));
            }
        }

        this.height = newHeight;
        this.data = newData;
    }

    /**
     * <p>getBufferedImage.</p>
     *
     * @return a {@link java.awt.image.BufferedImage} object.
     */
    public BufferedImage getBufferedImage() {
        final byte[] bytes = getData(true);

        if (bytes == null)
            return null;

        // make a a DEEP copy so we can't alter
        final int len = bytes.length;
        final byte[] copy = new byte[len];
        System.arraycopy(bytes, 0, copy, 0, len);

        // create an image from the raw data
        final DataBuffer db = new DataBufferByte(copy, copy.length);

        final WritableRaster raster = Raster.createPackedRaster(db, width, height, 1, null);

        final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY);
        image.setData(raster);

        return image;
    }

    /**
     * Faster BitSet implementation. Does not perfom any bound checks.
     * <p>
     * Author Boris von Loesch
     */
    static final class FastBitSet {
        static final int pot = 6;
        static final int mask = (int) ((-1L) >>> (Long.SIZE - pot));
        final long[] w;
        final int length;


        public FastBitSet(final int length) {
            this.length = length;
            int wcount = length / Long.SIZE;
            if (length % Long.SIZE != 0) wcount++;
            w = new long[wcount];
        }

        public int size() {
            return length;
        }

        public void setAll(final boolean value) {
            if (value)
                for (int i = 0; i < w.length; i++) {
                    w[i] = -1L;
                }
            else
                for (int i = 0; i < w.length; i++) {
                    w[i] = 0;
                }

        }

        public void set(final int start, final int end, final boolean value) {
            if (value) {
                for (int i = start; i < end; i++) {
                    set(i);
                }
            } else {
                for (int i = start; i < end; i++) {
                    clear(i);
                }
            }
        }

        public void or(int startindex, final FastBitSet set, final int startIndex, final int length) {
            int setStartIndex = startIndex;
            final int shift = startindex - setStartIndex;
            long k = set.w[setStartIndex >> pot];
            //Cyclic shift
            k = (k << shift) | (k >>> (Long.SIZE - shift));
            if ((setStartIndex & mask) + length <= Long.SIZE) {
                setStartIndex += shift;
                for (int i = 0; i < length; i++) {
                    w[(startindex) >>> pot] |= k & (1L << setStartIndex);
                    setStartIndex++;
                    startindex++;
                }
            } else {
                for (int i = 0; i < length; i++) {
                    if ((setStartIndex & mask) == 0) {
                        k = set.w[(setStartIndex) >> pot];
                        k = (k << shift) | (k >>> (Long.SIZE - shift));
                    }
                    w[(startindex) >>> pot] |= k & (1L << (setStartIndex + shift));
                    setStartIndex++;
                    startindex++;
                }
            }
        }

        public void set(final int index, final boolean value) {
            if (value) set(index);
            else clear(index);
        }

        public void set(final int index) {
            final int windex = index >>> pot;
            w[windex] |= (1L << index);
        }

        public void clear(final int index) {
            final int windex = index >>> pot;
            w[windex] &= ~(1L << index);
        }

        public boolean get(final int index) {
            final int windex = index >>> pot;
            return ((w[windex] & (1L << index)) != 0);
        }
    }
}