/**
 * ===========================================
 * Java Pdf Extraction Decoding Access Library
 * ===========================================
 *
 * Project Info:  http://www.jpedal.org
 * (C) Copyright 1997-2008, IDRsolutions and Contributors.
 * Main Developer: Simon Barnett
 *
 * 	This file is part of JPedal
 *
 * Copyright (c) 2008, IDRsolutions
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the IDRsolutions nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY IDRsolutions ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL IDRsolutions BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Other JBIG2 image decoding implementations include
 * jbig2dec (http://jbig2dec.sourceforge.net/)
 * xpdf (http://www.foolabs.com/xpdf/)
 * 
 * The final draft JBIG2 specification can be found at http://www.jpeg.org/public/fcd14492.pdf
 * 
 * All three of the above resources were used in the writing of this software, with methodologies,
 * processes and inspiration taken from all three.
 *
 * ---------------
 * JBIG2Bitmap.java
 * ---------------
 */
package org.jpedal.jbig2.image;

import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
// <start-me>
import java.awt.image.DataBufferByte;
// <end-me>
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.io.IOException;
import java.util.Arrays;
import java.util.BitSet;

import org.jpedal.jbig2.JBIG2Exception;
import org.jpedal.jbig2.decoders.ArithmeticDecoder;
import org.jpedal.jbig2.decoders.DecodeIntResult;
import org.jpedal.jbig2.decoders.HuffmanDecoder;
import org.jpedal.jbig2.decoders.JBIG2StreamDecoder;
import org.jpedal.jbig2.decoders.MMRDecoder;
import org.jpedal.jbig2.util.BinaryOperation;

public final class JBIG2Bitmap {

	private int width, height, line;
	private int bitmapNumber;
	//private FastBitSet data;

    //@sam - little task to investigate
//	private BitSet data;
	private byte[] newData;
//    boolean useByteArray=false;

	//private static int counter = 0;
	
	private ArithmeticDecoder arithmeticDecoder;
	private HuffmanDecoder huffmanDecoder;
	private MMRDecoder mmrDecoder;
	
	public JBIG2Bitmap(int width, int height, ArithmeticDecoder arithmeticDecoder, HuffmanDecoder huffmanDecoder, MMRDecoder mmrDecoder) {
		this.width = width;
		this.height = height;
		this.arithmeticDecoder = arithmeticDecoder;
		this.huffmanDecoder = huffmanDecoder;
		this.mmrDecoder = mmrDecoder;
		
		this.line = (width + 7) >> 3;

//		if (useByteArray)
		    this.newData = new byte[line * height];
//        else
//            this.data = new BitSet(width * height);
    }

	public void readBitmap(boolean useMMR, int template, boolean typicalPredictionGenericDecodingOn, boolean useSkip, JBIG2Bitmap skipBitmap, short[] adaptiveTemplateX, short[] adaptiveTemplateY, int mmrDataLength) throws IOException, JBIG2Exception {

		if (useMMR) {

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

			int[] referenceLine = new int[width + 2];
			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)
							System.out.println("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)
						System.out.println("Missing EOFB in JBIG2 MMR bitmap data");
				}
			}

		} else {

			//ArithmeticDecoder arithmeticDecoder = ArithmeticDecoder.getInstance();

			BitmapPointer cxPtr0 = new BitmapPointer(this), cxPtr1 = new BitmapPointer(this);
			BitmapPointer atPtr0 = new BitmapPointer(this), atPtr1 = new BitmapPointer(this), atPtr2 = new BitmapPointer(this), 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;
				}
			}

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

			for (int row = 0; row < height; row++) {
				if (typicalPredictionGenericDecodingOn) {
					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();
					cx0 = (BinaryOperation.bit32Shift(cx0, 1, BinaryOperation.LEFT_SHIFT)) | cxPtr0.nextPixel();

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

					cx1 = (BinaryOperation.bit32Shift(cx1, 1, BinaryOperation.LEFT_SHIFT)) | cxPtr1.nextPixel();
					cx1 = (BinaryOperation.bit32Shift(cx1, 1, BinaryOperation.LEFT_SHIFT)) | 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.bit32Shift(cx0, 13, BinaryOperation.LEFT_SHIFT)) | (BinaryOperation.bit32Shift(cx1, 8, BinaryOperation.LEFT_SHIFT)) | (BinaryOperation.bit32Shift(cx2, 4, BinaryOperation.LEFT_SHIFT)) | (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) {
								setPixel(col, row, 1);
							}
						}

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

				case 1:

					cxPtr0.setPointer(0, row - 2);
					cx0 = cxPtr0.nextPixel();
					cx0 = (BinaryOperation.bit32Shift(cx0, 1, BinaryOperation.LEFT_SHIFT)) | cxPtr0.nextPixel();
					cx0 = (BinaryOperation.bit32Shift(cx0, 1, BinaryOperation.LEFT_SHIFT)) | cxPtr0.nextPixel();

					cxPtr1.setPointer(0, row - 1);
					cx1 = cxPtr1.nextPixel();
					cx1 = (BinaryOperation.bit32Shift(cx1, 1, BinaryOperation.LEFT_SHIFT)) | cxPtr1.nextPixel();
					cx1 = (BinaryOperation.bit32Shift(cx1, 1, BinaryOperation.LEFT_SHIFT)) | cxPtr1.nextPixel();

					cx2 = 0;

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

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

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

						if (useSkip && skipBitmap.getPixel(col, row) != 0) {
							pixel = 0;
						} else {
							pixel = arithmeticDecoder.decodeBit(cx, arithmeticDecoder.genericRegionStats);
							if (pixel != 0) {
								setPixel(col, row, 1);
							}
						}

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

				case 2:

					cxPtr0.setPointer(0, row - 2);
					cx0 = cxPtr0.nextPixel();
					cx0 = (BinaryOperation.bit32Shift(cx0, 1, BinaryOperation.LEFT_SHIFT)) | cxPtr0.nextPixel();

					cxPtr1.setPointer(0, row - 1);
					cx1 = cxPtr1.nextPixel();
					cx1 = (BinaryOperation.bit32Shift(cx1, 1, BinaryOperation.LEFT_SHIFT)) | cxPtr1.nextPixel();

					cx2 = 0;

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

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

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

						if (useSkip && skipBitmap.getPixel(col, row) != 0) {
							pixel = 0;
						} else {
							pixel = arithmeticDecoder.decodeBit(cx, arithmeticDecoder.genericRegionStats);
							if (pixel != 0) {
								setPixel(col, row, 1);
							}
						}

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

				case 3:

					cxPtr1.setPointer(0, row - 1);
					cx1 = cxPtr1.nextPixel();
					cx1 = (BinaryOperation.bit32Shift(cx1, 1, BinaryOperation.LEFT_SHIFT)) | cxPtr1.nextPixel();

					cx2 = 0;

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

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

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

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

						} else {
							pixel = arithmeticDecoder.decodeBit(cx, arithmeticDecoder.genericRegionStats);
							if (pixel != 0) {
								setPixel(col, row, 1);
							}
						}

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

	public void readGenericRefinementRegion(int template, boolean typicalPredictionGenericRefinementOn, JBIG2Bitmap referredToBitmap, int referenceDX, int referenceDY, short[] adaptiveTemplateX, short[] adaptiveTemplateY) throws IOException, JBIG2Exception {

		//ArithmeticDecoder arithmeticDecoder = ArithmeticDecoder.getInstance();

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

		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.bit32Shift(cx3, 1, BinaryOperation.LEFT_SHIFT)) | 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.bit32Shift(typicalPredictionGenericRefinementCX0, 1, BinaryOperation.LEFT_SHIFT)) | typicalPredictionGenericRefinementCXPtr0.nextPixel();
					typicalPredictionGenericRefinementCX0 = (BinaryOperation.bit32Shift(typicalPredictionGenericRefinementCX0, 1, BinaryOperation.LEFT_SHIFT)) | typicalPredictionGenericRefinementCXPtr0.nextPixel();

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

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

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

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

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

						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.bit32Shift(cx0, 7, BinaryOperation.LEFT_SHIFT)) | (cxPtr1.nextPixel() << 6) | (cxPtr2.nextPixel() << 5) | (BinaryOperation.bit32Shift(cx3, 2, BinaryOperation.LEFT_SHIFT)) | cx4;

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

			} 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.bit32Shift(cx3, 1, BinaryOperation.LEFT_SHIFT)) | cxPtr3.nextPixel();

				cxPtr4.setPointer(-1 - referenceDX, row + 1 - referenceDY);
				cx4 = cxPtr4.nextPixel();
				cx4 = (BinaryOperation.bit32Shift(cx4, 1, BinaryOperation.LEFT_SHIFT)) | 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.bit32Shift(typicalPredictionGenericRefinementCX0, 1, BinaryOperation.LEFT_SHIFT)) | typicalPredictionGenericRefinementCXPtr0.nextPixel();
					typicalPredictionGenericRefinementCX0 = (BinaryOperation.bit32Shift(typicalPredictionGenericRefinementCX0, 1, BinaryOperation.LEFT_SHIFT)) | typicalPredictionGenericRefinementCXPtr0.nextPixel();

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

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

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

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

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

						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.bit32Shift(cx0, 11, BinaryOperation.LEFT_SHIFT)) | (cxPtr1.nextPixel() << 10) | (BinaryOperation.bit32Shift(cx2, 8, BinaryOperation.LEFT_SHIFT)) | (BinaryOperation.bit32Shift(cx3, 5, BinaryOperation.LEFT_SHIFT)) | (BinaryOperation.bit32Shift(cx4, 2, BinaryOperation.LEFT_SHIFT)) | (cxPtr5.nextPixel() << 1) | cxPtr6.nextPixel();

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

	public void readTextRegion(boolean huffman, boolean symbolRefine, int noOfSymbolInstances, int logStrips, int noOfSymbols, int[][] symbolCodeTable, int symbolCodeLength, JBIG2Bitmap[] symbols, int defaultPixel, int combinationOperator, boolean transposed, int referenceCorner, int sOffset, int[][] huffmanFSTable, int[][] huffmanDSTable, int[][] huffmanDTTable, int[][] huffmanRDWTable, int[][] huffmanRDHTable, int[][] huffmanRDXTable, int[][] huffmanRDYTable, int[][] huffmanRSizeTable, int template, short[] symbolRegionAdaptiveTemplateX,
			short[] symbolRegionAdaptiveTemplateY, JBIG2StreamDecoder decoder) throws JBIG2Exception, IOException {

		JBIG2Bitmap symbolBitmap;

		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;

				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)
						System.out.println("Invalid symbol number in JBIG2 text region");
				} else {
					symbolBitmap = null;

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

						int refinementDeltaWidth, refinementDeltaHeight, refinementDeltaX, 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];
					}

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

				currentInstance++;

				DecodeIntResult decodeIntResult;

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

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

				ds = decodeIntResult.intResult();

				s += sOffset + ds;
			}
		}
	}

	public void clear(int defPixel) {
//		if (useByteArray) {
            newData = new byte[newData.length];
            byte value = defPixel==0 ? (byte)0 : (byte)-128;
            for (int i=0; i< newData.length; i++) {
                newData[i]=value;
            }
//        } else {
//            data.set(0, data.size(), defPixel == 1);
//        }
	}

	public void combine(JBIG2Bitmap bitmap, int x, int y, long combOp) {
		int srcWidth = bitmap.width;
		int srcHeight = bitmap.height;
		int srcRow = 0, srcCol = 0;
		
//		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++;
		
		for (int row = y; row < y + srcHeight; row++) {
			for (int col = x; col < x + srcWidth; col++) {

				int srcPixel = bitmap.getPixel(srcCol, srcRow);

				switch ((int) combOp) {
				case 0: // or
					setPixel(col, row, getPixel(col, row, line, newData) | srcPixel);
					break;
				case 1: // and
					setPixel(col, row, getPixel(col, row, line, newData) & srcPixel);
					break;
				case 2: // xor
					setPixel(col, row, getPixel(col, row, line, newData) ^ srcPixel);
					break;
				case 3: // xnor
					if ((getPixel(col, row, line, newData) == 1 && srcPixel == 1) || (getPixel(col, row, line, newData) == 0 && srcPixel == 0))
						setPixel(col, row, 1);
					else
						setPixel(col, row, 0);

					break;
				case 4: // replace
					setPixel(col, row, srcPixel);
					break;
				}
				srcCol++;
			}

			srcCol = 0;
			srcRow++;
		}
	}

	/**
	 * set a full byte of pixels
	 */
//	private void setPixelByte(int col, int row, byte bits) {
		//data.setByte(row, col, bits);
//	}

	/**
	 * get a byte of pixels
	 */
//	public byte getPixelByte(int col, int row) {
		//return data.getByte(row, col);
//	}

	private void duplicateRow(int yDest, int ySrc) {
//		for (int i = 0; i < width;) {
//			setPixelByte(i, yDest, getPixelByte(i, ySrc));
//			i += 8;
//		}
		for (int i = 0; i < width; i++) {
			setPixel(i, yDest, getPixel(i, ySrc, line, newData));
		}
	}

	public int getWidth() {
		return width;
	}

	public int getHeight() {
		return height;
	}

	public byte[] getData(boolean switchPixelColor) {
//		byte[] bytes = new byte[height * line];
//
//		for (int i = 0; i < height; i++) {
//			System.arraycopy(data.bytes[i], 0, bytes, line * i, line);
//		}
//
//		for (int i = 0; i < bytes.length; i++) {
//			// reverse bits
//
//			int value = bytes[i];
//			value = (value & 0x0f) << 4 | (value & 0xf0) >> 4;
//			value = (value & 0x33) << 2 | (value & 0xcc) >> 2;
//			value = (value & 0x55) << 1 | (value & 0xaa) >> 1;
//
//			if (switchPixelColor) {
//				value ^= 0xff;
//			}
//
//			bytes[i] = (byte) (value & 0xFF);
//		}
//
//		return bytes;
        byte[] bytes;
//        if (useByteArray) {
            bytes = newData;
//        } else {
//            bytes = new byte[height * line];
//
//            int count = 0, offset = 0;
//            for (int row = 0; row < height; row++) {
//                for (int col = 0; col < width; col++) {
//                    if (data.get(count)) {
//                        int bite = (count + offset) / 8;
//                        int bit = (count + offset) % 8;
//
//                        bytes[bite] |= 1 << (7 - bit);
//                    }
//                    count++;
//                }
//
//                offset = (line * 8 * (row + 1)) - count;
//            }
//        }

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

		return bytes;
	}

	public JBIG2Bitmap getSlice(int x, int y, int width, int height) {
//		JBIG2Bitmap slice = new JBIG2Bitmap(width, height);
//
//		int sliceRow = 0, sliceCol = 0;
//		int maxCol = x + width;
//		
//		//ShowGUIMessage.showGUIMessage("x", this.getBufferedImage(), "xx");
//		
//		System.out.println(">>> getSlice x = "+x+" y = "+y+ " width = "+width+ " height = "+height);
//		System.out.println(">>> baseImage width = "+this.width+ " height = "+this.height);
//		
//		System.out.println("counter = "+counter);
//		if(counter == 17){
//			System.out.println();
//			//ShowGUIMessage.showGUIMessage("x", this.getBufferedImage(), "xx");
//		}
//		
//		ShowGUIMessage.showGUIMessage("x", this.getBufferedImage(), "xx");
//		
//		for (int row = y; row < height; row++) {
//			for (int col = x; col < maxCol; col += 8, sliceCol += 8) {
//				slice.setPixelByte(sliceCol, sliceRow, getPixelByte(col, row));
//				//if(counter > 10)
//					//ShowGUIMessage.showGUIMessage("new", slice.getBufferedImage(), "new");
//			}
//			sliceCol = 0;
//			sliceRow++;
//		}
//		counter++;
//
//		ShowGUIMessage.showGUIMessage("new", slice.getBufferedImage(), "new");
//		
//		return slice;
		
		JBIG2Bitmap slice = new JBIG2Bitmap(width, height, arithmeticDecoder, huffmanDecoder, mmrDecoder);

		int sliceRow = 0, sliceCol = 0;
		for (int row = y; row < height; row++) {
			for (int col = x; col < x + width; col++) {
				//System.out.println("row = "+row +" column = "+col);
				slice.setPixel(sliceCol, sliceRow, getPixel(col, row, line, newData));
				sliceCol++;
			}
			sliceCol = 0;
			sliceRow++;
		}

		return slice;
	}

	/**
	private static void setPixel(int col, int row, FastBitSet data, int value) {
		if (value == 1)
			data.set(row, col);
		else
			data.clear(row, col);
	}/**/

//	private void setPixelByte(int col, int row, FastBitSet data, byte bits) {
//		data.setByte(row, col, bits);
//	}

//	public void setPixel(int col, int row, int value) {
//		setPixel(col, row, data, value);
//	}

//	public int getPixel(int col, int row) {
//		return data.get(row, col) ? 1 : 0;
//	}

//	private void setPixel(int col, int row, BitSet data, int value) {
//		int index = (row * width) + col;
//
//		data.set(index, value == 1);
//	}

    private void newSetPixel(int col, int row, byte[] data, int value) {
        int index = (row * line) + (col / 8);
        int b = data[index];

        if (value==0)
            b = b & ~(1 << 7-(col%8));
        else
            b = b | (1 << 7-(col%8));

        data[index] = (byte)b;
    }

	public void setPixel(int col, int row, int value) {
//        if (useByteArray)
            newSetPixel(col,row,newData,value);
//        else
//            setPixel(col, row, data, value);
	}

        private final static int getPixel(int col, int row, int line, byte[] data) {
            return (data[(row * line) + (col / 8)] & 1 << 7-(col%8)) != 0 ? 1 : 0;
        }

	public int getPixel(int col, int row) {
//        if (useByteArray)
//            return getPixel(col,row,line,newData);
		    return (newData[(row * line) + (col / 8)] & 1 << 7-(col%8)) != 0 ? 1 : 0;
//        else
//            return data.get((row * width) + col) ? 1 : 0;
	}
	
	public void expand(int newHeight, int defaultPixel) {
//		System.out.println("expand FastBitSet");
//		FastBitSet newData = new FastBitSet(width, newHeight);
//
//		for (int row = 0; row < height; row++) {
//			for (int col = 0; col < width; col += 8) {
//				setPixelByte(col, row, newData, getPixelByte(col, row));
//			}
//		}
//
//		this.height = newHeight;
//		this.data = newData;
//        if (useByteArray) {
            byte[] newNewData = new byte[newHeight * line];

            System.arraycopy(newData,0,newNewData,0,height*line);

            this.height = newHeight;
            this.newData = newNewData;
//        } else {
//
//            BitSet newData = new BitSet(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;
//        }
    }

	public void setBitmapNumber(int segmentNumber) {
		this.bitmapNumber = segmentNumber;
	}

	public int getBitmapNumber() {
		return bitmapNumber;
	}

	public BufferedImage getBufferedImage() {
		byte[] bytes = getData(true);

		if (bytes == null)
			return null;

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

		BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_BINARY);
		// <start-me>
		/** create an image from the raw data */
		DataBuffer db = new DataBufferByte(copy, copy.length);
		WritableRaster raster = Raster.createPackedRaster(db, width, height, 1, null);
		/* <end-me>
        WritableRaster raster = image.getRaster();
        int[] intrgb = new int[copy.length];
        for(int i=0;i<copy.length;i++){
            intrgb[i] = (int)copy[i];
        }
        raster.setPixels(0, 0, width, height, intrgb);
        /**/

		image.setData(raster);

		return image;
	}

	static final class FastBitSet {
		byte[][] bytes;

		int w, h;
		
		public FastBitSet(int width, int height) {
			bytes = new byte[height][(width + 7) / 8];
			this.w = width;
			this.h = height;
			//System.out.println("width = "+width+" height = "+height);
		}

//		public int getByte(int row, int col) {
//			
//			System.out.println("(width + 7) / 8 = " + (width + 7) / 8);
//			System.out.println("external width = " + width + " external height = " + height);
//			System.out.println("internal width = " + w + " internal height = " + h);
//			System.out.println("row = " + row + " column = " + col);
//			
//			int offset = col / 8;
//			int mod = col % 8;
//
//			System.out.println("offset = " + offset + " mod = " + mod+" bytes[row].length = "+bytes[row].length);
//			
//			if (mod == 0)
//				return bytes[row][offset];
//
//			if(offset == bytes[row].length - 1){
//				System.out.println("returning");
//				return ((bytes[row][offset] & 0xFF) >> mod);
//			}
//			
//			int left = ((bytes[row][offset] & 0xFF) >> mod);
//			int right = ((bytes[row][offset + 1] & 0xFF) << (8 - mod));
//			
//			return left | right;
//		}

//		public void setByte(int row, int col, int bits) {
//			int offset = col / 8;
//			int mod = col % 8;
//
//			System.out.println("setByte offset = " + offset + " mod = " + mod);
//			
//			
//			if (mod == 0)
//				bytes[row][offset] = (byte) bits;
//			else {
//				int mask = 0xFF >> (8 - mod);
//				System.out.println("setByte mask = " + mask);
//				bytes[row][offset] = (byte) ((bytes[row][offset] & mask) | ((bits & 0xFF) << mod));
//				bytes[row][offset + 1] = (byte) ((bytes[row][offset + 1] & ~mask) | ((bits & 0xFF) >> (8 - mod)));
//			}
//		}

		public byte getByte(int row, int col) {
//			System.out.println("(width + 7) / 8 = " + (width + 7) / 8);
//			System.out.println("external width = " + width + " external height = " + height);
//			System.out.println("internal width = " + w + " internal height = " + h);
//			System.out.println("row = " + row + " column = " + col);
			
			int offset = col / 8;
			int mod = col % 8;

//			System.out.println("offset = " + offset + " mod = " + mod+" bytes[row].length = "+bytes[row].length);
			
			if (mod == 0)
				return bytes[row][offset];

//			if(offset == bytes[row].length - 1){
//				System.out.println("returning");
//				return ((bytes[row][offset] & 0xFF) >> mod);
//			}
			
			byte leftMask = (byte) (0xFF >> (8 - mod));
			byte rightMask = (byte) (0xFF << mod);
			
			byte left = (byte) ((bytes[row][offset] & leftMask) << (8 - mod));
			
			if(offset + 1 >= bytes[row].length){
				System.out.println("returning");
				return left;
			}
			
			byte right = (byte) ((bytes[row][offset + 1] & rightMask) >> mod);
			
			return (byte) (left | right);
		}
		
		public void setByte(int row, int col, byte bits) {
			int offset = col / 8;
			int mod = col % 8;

			//System.out.println("setByte offset = " + offset + " mod = " + mod);
			
			
			if (mod == 0)
				bytes[row][offset] = (byte) bits;
			else {
				
				byte left = (byte) (bits >> mod);
				byte leftMask = (byte) (0xFF << (8 - mod));
				
				bytes[row][offset] &= leftMask;
				bytes[row][offset] |= left;
				
				if(offset + 1 >= bytes[row].length)
					return;
				
				byte right = (byte) (bits << (8 - mod));
				byte rightMask = (byte) (0xFF >> mod);
				
				bytes[row][offset + 1] &= rightMask;
				bytes[row][offset + 1] |= right;
				
//				int mask = 0xFF >> (8 - mod);
//				System.out.println("setByte mask = " + mask);
//				bytes[row][offset] = (byte) ((bytes[row][offset] & mask) | ((bits & 0xFF) << mod));
//				bytes[row][offset + 1] = (byte) ((bytes[row][offset + 1] & ~mask) | ((bits & 0xFF) >> (8 - mod)));
			}
		}
		
		public void set(int row, int col) {
			byte bit = (byte) (1 << (col % 8));
			bytes[row][col / 8] |= bit;
		}

		public void clear(int row, int col) {
			byte bit = (byte) (1 << (col % 8));
			bytes[row][col / 8] &= ~bit;
		}

		public boolean get(int row, int col) {
			byte bit = (byte) (1 << (col % 8));
			return (bytes[row][col / 8] & bit) != 0;
		}

		public void reset(boolean set) {
			for (int i = 0; i < bytes.length; i++)
				Arrays.fill(bytes[i], set ? (byte) 0xFF : (byte) 0x00);
		}
	}
}
