/*
 *
 * This file is part of the iText (R) project.
    Copyright (c) 1998-2022 iText Group NV
 * Authors: Bruno Lowagie, Paulo Soares, et al.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License version 3
 * as published by the Free Software Foundation with the addition of the
 * following permission added to Section 15 as permitted in Section 7(a):
 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
 * ITEXT GROUP. ITEXT GROUP DISCLAIMS THE WARRANTY OF NON INFRINGEMENT
 * OF THIRD PARTY RIGHTS
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero General Public License for more details.
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA, 02110-1301 USA, or download the license from the following URL:
 * http://itextpdf.com/terms-of-use/
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License,
 * a covered work must retain the producer line in every PDF that is created
 * or manipulated using iText.
 *
 * You can be released from the requirements of the license by purchasing
 * a commercial license. Buying such a license is mandatory as soon as you
 * develop commercial activities involving the iText software without
 * disclosing the source code of your own applications.
 * These activities include: offering paid services to customers as an ASP,
 * serving PDFs on the fly in a web application, shipping iText with a closed
 * source product.
 *
 * For more information, please contact iText Software Corp. at this
 * address: sales@itextpdf.com
 */
package com.itextpdf.text;

import com.itextpdf.awt.PdfGraphics2D;
import com.itextpdf.text.api.Indentable;
import com.itextpdf.text.api.Spaceable;
import com.itextpdf.text.error_messages.MessageLocalization;
import com.itextpdf.text.io.RandomAccessSourceFactory;
import com.itextpdf.text.pdf.ICC_Profile;
import com.itextpdf.text.pdf.PRIndirectReference;
import com.itextpdf.text.pdf.PdfArray;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfDictionary;
import com.itextpdf.text.pdf.PdfIndirectReference;
import com.itextpdf.text.pdf.PdfName;
import com.itextpdf.text.pdf.PdfNumber;
import com.itextpdf.text.pdf.PdfOCG;
import com.itextpdf.text.pdf.PdfObject;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfStream;
import com.itextpdf.text.pdf.PdfString;
import com.itextpdf.text.pdf.PdfTemplate;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.text.pdf.RandomAccessFileOrArray;
import com.itextpdf.text.pdf.codec.BmpImage;
import com.itextpdf.text.pdf.codec.CCITTG4Encoder;
import com.itextpdf.text.pdf.codec.GifImage;
import com.itextpdf.text.pdf.codec.JBIG2Image;
import com.itextpdf.text.pdf.codec.PngImage;
import com.itextpdf.text.pdf.codec.TiffImage;
import com.itextpdf.text.pdf.interfaces.IAccessibleElement;
import com.itextpdf.text.pdf.interfaces.IAlternateDescription;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;

/**
 * An <CODE>Image</CODE> is the representation of a graphic element (JPEG, PNG
 * or GIF) that has to be inserted into the document
 *
 * @see Element
 * @see Rectangle
 */

public abstract class Image extends Rectangle implements Indentable, Spaceable, IAccessibleElement, IAlternateDescription {

	// static final membervariables

	/** this is a kind of image alignment. */
	public static final int DEFAULT = 0;

	/** this is a kind of image alignment. */
	public static final int RIGHT = 2;

	/** this is a kind of image alignment. */
	public static final int LEFT = 0;

	/** this is a kind of image alignment. */
	public static final int MIDDLE = 1;

	/** this is a kind of image alignment. */
	public static final int TEXTWRAP = 4;

	/** this is a kind of image alignment. */
	public static final int UNDERLYING = 8;

	/** This represents a coordinate in the transformation matrix. */
	public static final int AX = 0;

	/** This represents a coordinate in the transformation matrix. */
	public static final int AY = 1;

	/** This represents a coordinate in the transformation matrix. */
	public static final int BX = 2;

	/** This represents a coordinate in the transformation matrix. */
	public static final int BY = 3;

	/** This represents a coordinate in the transformation matrix. */
	public static final int CX = 4;

	/** This represents a coordinate in the transformation matrix. */
	public static final int CY = 5;

	/** This represents a coordinate in the transformation matrix. */
	public static final int DX = 6;

	/** This represents a coordinate in the transformation matrix. */
	public static final int DY = 7;

	/** type of image */
	public static final int ORIGINAL_NONE = 0;

	/** type of image */
	public static final int ORIGINAL_JPEG = 1;

	/** type of image */
	public static final int ORIGINAL_PNG = 2;

	/** type of image */
	public static final int ORIGINAL_GIF = 3;

	/** type of image */
	public static final int ORIGINAL_BMP = 4;

	/** type of image */
	public static final int ORIGINAL_TIFF = 5;

	/** type of image */
	public static final int ORIGINAL_WMF = 6;

	/** type of image */
    public static final int ORIGINAL_PS = 7;

	/** type of image */
	public static final int ORIGINAL_JPEG2000 = 8;

	/**
	 * type of image
	 * @since	2.1.5
	 */
	public static final int ORIGINAL_JBIG2 = 9;

    // member variables

	/** The image type. */
	protected int type;

	/** The URL of the image. */
	protected URL url;

	/** The raw data of the image. */
	protected byte rawData[];

	/** The bits per component of the raw image. It also flags a CCITT image. */
	protected int bpc = 1;

	/** The template to be treated as an image. */
	protected PdfTemplate template[] = new PdfTemplate[1];

	/** The alignment of the Image. */
	protected int alignment;

	/** Text that can be shown instead of the image. */
	protected String alt;

	/** This is the absolute X-position of the image. */
	protected float absoluteX = Float.NaN;

	/** This is the absolute Y-position of the image. */
	protected float absoluteY = Float.NaN;

	/** This is the width of the image without rotation. */
	protected float plainWidth;

	/** This is the width of the image without rotation. */
	protected float plainHeight;

	/** This is the scaled width of the image taking rotation into account. */
	protected float scaledWidth;

	/** This is the original height of the image taking rotation into account. */
	protected float scaledHeight;

    /**
     * The compression level of the content streams.
     * @since	2.1.3
     */
    protected int compressionLevel = PdfStream.DEFAULT_COMPRESSION;

	/** an iText attributed unique id for this image. */
	protected Long mySerialId = getSerialId();

    protected PdfName role = PdfName.FIGURE;
    protected HashMap<PdfName, PdfObject> accessibleAttributes = null;
    private AccessibleElementId id = null;


	// image from file or URL

	/**
	 * Constructs an <CODE>Image</CODE> -object, using an <VAR>url </VAR>.
	 *
	 * @param url
	 *            the <CODE>URL</CODE> where the image can be found.
	 */
	public Image(final URL url) {
		super(0, 0);
		this.url = url;
		this.alignment = DEFAULT;
		rotationRadians = 0;
	}
    
    public static Image getInstance(final URL url) throws BadElementException, MalformedURLException, IOException {
        return Image.getInstance(url, false);
    }

	/**
	 * Gets an instance of an Image.
	 *
	 * @param url
	 *            an URL
	 * @return an Image
	 * @throws BadElementException
	 * @throws MalformedURLException
	 * @throws IOException
	 */
	public static Image getInstance(final URL url, boolean recoverFromImageError) throws BadElementException,
			MalformedURLException, IOException {
		InputStream is = null;
        RandomAccessSourceFactory randomAccessSourceFactory = new RandomAccessSourceFactory();

		try {
			is = url.openStream();
			int c1 = is.read();
			int c2 = is.read();
			int c3 = is.read();
			int c4 = is.read();
			// jbig2
			int c5 = is.read();
			int c6 = is.read();
			int c7 = is.read();
			int c8 = is.read();
			is.close();

			is = null;
			if (c1 == 'G' && c2 == 'I' && c3 == 'F') {
				GifImage gif = new GifImage(url);
				Image img = gif.getImage(1);
				return img;
			}
			if (c1 == 0xFF && c2 == 0xD8) {
				return new Jpeg(url);
			}
			if (c1 == 0x00 && c2 == 0x00 && c3 == 0x00 && c4 == 0x0c) {
				return new Jpeg2000(url);
			}
			if (c1 == 0xff && c2 == 0x4f && c3 == 0xff && c4 == 0x51) {
				return new Jpeg2000(url);
			}
			if (c1 == PngImage.PNGID[0] && c2 == PngImage.PNGID[1]
					&& c3 == PngImage.PNGID[2] && c4 == PngImage.PNGID[3]) {
				return PngImage.getImage(url);
			}
			if (c1 == 0xD7 && c2 == 0xCD) {
				return new ImgWMF(url);
			}
			if (c1 == 'B' && c2 == 'M') {
				return  BmpImage.getImage(url);
			}
			if (c1 == 'M' && c2 == 'M' && c3 == 0 && c4 == 42
					|| c1 == 'I' && c2 == 'I' && c3 == 42 && c4 == 0) {
				RandomAccessFileOrArray ra = null;
				try {
					if (url.getProtocol().equals("file")) {
						String file = url.getFile();
                        file = Utilities.unEscapeURL(file);
						ra = new RandomAccessFileOrArray(randomAccessSourceFactory.createBestSource(file));
					} else
						ra = new RandomAccessFileOrArray(randomAccessSourceFactory.createSource(url));
					Image img = TiffImage.getTiffImage(ra, 1);
					img.url = url;
					return img;
				} catch (RuntimeException e ) {
                    if ( recoverFromImageError ) {
                        // reruns the getTiffImage() with several error recovering workarounds in place
                        // not guaranteed to work with every TIFF
                        Image img = TiffImage.getTiffImage(ra, recoverFromImageError, 1);
                        img.url = url;
                        return img;
                    }
                    throw e;
                } finally {
					if (ra != null)
						ra.close();
				}

			}
			if ( c1 == 0x97 && c2 == 'J' && c3 == 'B' && c4 == '2' &&
					c5 == '\r' && c6 == '\n' && c7 == 0x1a && c8 == '\n' ) {
				RandomAccessFileOrArray ra = null;
				try {
					if (url.getProtocol().equals("file")) {
						String file = url.getFile();
						file = Utilities.unEscapeURL(file);
			            ra = new RandomAccessFileOrArray(randomAccessSourceFactory.createBestSource(file));
					} else
						ra = new RandomAccessFileOrArray(randomAccessSourceFactory.createSource(url));
					Image img = JBIG2Image.getJbig2Image(ra, 1);
					img.url = url;
					return img;
				} finally {
						if (ra != null)
							ra.close();
				}
			}
			throw new IOException(MessageLocalization.getComposedMessage("unknown.image.format", url.toString()));
		} finally {
			if (is != null) {
				is.close();
			}
		}
	}

	/**
	 * Gets an instance of an Image.
	 *
	 * @param filename
	 *            a filename
	 * @return an object of type <CODE>Gif</CODE>,<CODE>Jpeg</CODE> or
	 *         <CODE>Png</CODE>
	 * @throws BadElementException
	 * @throws MalformedURLException
	 * @throws IOException
	 */
	public static Image getInstance(final String filename)
			throws BadElementException, MalformedURLException, IOException {
		return getInstance(Utilities.toURL(filename));
	}
    
    public static Image getInstance(final String filename, boolean recoverFromImageError) throws IOException, BadElementException {
        return getInstance(Utilities.toURL(filename), recoverFromImageError);
    }


    public static Image getInstance(final byte imgb[]) throws BadElementException,
            MalformedURLException, IOException {
        return getInstance(imgb, false);
    }

	/**
	 * gets an instance of an Image
	 *
	 * @param imgb
	 *            raw image date
	 * @return an Image object
	 * @throws BadElementException
	 * @throws MalformedURLException
	 * @throws IOException
	 */
	public static Image getInstance(final byte imgb[], boolean recoverFromImageError) throws BadElementException,
			MalformedURLException, IOException {
		InputStream is = null;
        RandomAccessSourceFactory randomAccessSourceFactory = new RandomAccessSourceFactory();
		try {
			is = new java.io.ByteArrayInputStream(imgb);
			int c1 = is.read();
			int c2 = is.read();
			int c3 = is.read();
			int c4 = is.read();
			is.close();

			is = null;
			if (c1 == 'G' && c2 == 'I' && c3 == 'F') {
				GifImage gif = new GifImage(imgb);
				return gif.getImage(1);
			}
			if (c1 == 0xFF && c2 == 0xD8) {
				return new Jpeg(imgb);
			}
			if (c1 == 0x00 && c2 == 0x00 && c3 == 0x00 && c4 == 0x0c) {
				return new Jpeg2000(imgb);
			}
			if (c1 == 0xff && c2 == 0x4f && c3 == 0xff && c4 == 0x51) {
				return new Jpeg2000(imgb);
			}
			if (c1 == PngImage.PNGID[0] && c2 == PngImage.PNGID[1]
					&& c3 == PngImage.PNGID[2] && c4 == PngImage.PNGID[3]) {
				return PngImage.getImage(imgb);
			}
			if (c1 == 0xD7 && c2 == 0xCD) {
				return new ImgWMF(imgb);
			}
			if (c1 == 'B' && c2 == 'M') {
				return BmpImage.getImage(imgb);
			}
			if (c1 == 'M' && c2 == 'M' && c3 == 0 && c4 == 42
					|| c1 == 'I' && c2 == 'I' && c3 == 42 && c4 == 0) {
				RandomAccessFileOrArray ra = null;
				try {
					ra = new RandomAccessFileOrArray(randomAccessSourceFactory.createSource(imgb));
					Image img = TiffImage.getTiffImage(ra, 1);
                    if (img.getOriginalData() == null)
                        img.setOriginalData(imgb);
					return img;
				} catch ( RuntimeException e ) {
                    if ( recoverFromImageError ) {
                        // reruns the getTiffImage() with several error recovering workarounds in place
                        // not guaranteed to work with every TIFF
                        Image img = TiffImage.getTiffImage(ra, recoverFromImageError, 1);
                        if (img.getOriginalData() == null)
                            img.setOriginalData(imgb);
                        return img;
                    }
                    throw e;
                } finally {
					if (ra != null)
						ra.close();
				}

			}
			if ( c1 == 0x97 && c2 == 'J' && c3 == 'B' && c4 == '2' ) {
				is = new java.io.ByteArrayInputStream(imgb);
				is.skip(4);
				int c5 = is.read();
				int c6 = is.read();
				int c7 = is.read();
				int c8 = is.read();
                is.close();
				if ( c5 == '\r' && c6 == '\n' && c7 == 0x1a && c8 == '\n' ) {
					// a jbig2 file with a file header.  the header is the only way we know here.
					// embedded jbig2s don't have a header, have to create them by explicit use of Jbig2Image?
					// nkerr, 2008-12-05  see also the getInstance(URL)
					RandomAccessFileOrArray ra = null;
					try {
						ra = new RandomAccessFileOrArray(randomAccessSourceFactory.createSource(imgb));
						Image img = JBIG2Image.getJbig2Image(ra, 1);
						if (img.getOriginalData() == null)
							img.setOriginalData(imgb);
						return img;
					} finally {
						if (ra != null)
							ra.close();
					}
				}
			}
			throw new IOException(MessageLocalization.getComposedMessage("the.byte.array.is.not.a.recognized.imageformat"));
		} finally {
			if (is != null) {
				is.close();
			}
		}
	}

	/**
	 * Gets an instance of an Image in raw mode.
	 *
	 * @param width
	 *            the width of the image in pixels
	 * @param height
	 *            the height of the image in pixels
	 * @param components
	 *            1,3 or 4 for GrayScale, RGB and CMYK
	 * @param data
	 *            the image data
	 * @param bpc
	 *            bits per component
	 * @return an object of type <CODE>ImgRaw</CODE>
	 * @throws BadElementException
	 *             on error
	 */
	public static Image getInstance(final int width, final int height, final int components,
			final int bpc, final byte data[]) throws BadElementException {
		return Image.getInstance(width, height, components, bpc, data, null);
	}

	/**
	 * Creates a JBIG2 Image.
	 * @param	width	the width of the image
	 * @param	height	the height of the image
	 * @param	data	the raw image data
	 * @param	globals	JBIG2 globals
	 * @return the Image
	 * @since	2.1.5
	 */
	public static Image getInstance(final int width, final int height, final byte[] data, final byte[] globals) {
		return new ImgJBIG2(width, height, data, globals);
	}

	/**
	 * Creates an Image with CCITT G3 or G4 compression. It assumes that the
	 * data bytes are already compressed.
	 *
	 * @param width
	 *            the exact width of the image
	 * @param height
	 *            the exact height of the image
	 * @param reverseBits
	 *            reverses the bits in <code>data</code>. Bit 0 is swapped
	 *            with bit 7 and so on
	 * @param typeCCITT
	 *            the type of compression in <code>data</code>. It can be
	 *            CCITTG4, CCITTG31D, CCITTG32D
	 * @param parameters
	 *            parameters associated with this stream. Possible values are
	 *            CCITT_BLACKIS1, CCITT_ENCODEDBYTEALIGN, CCITT_ENDOFLINE and
	 *            CCITT_ENDOFBLOCK or a combination of them
	 * @param data
	 *            the image data
	 * @return an Image object
	 * @throws BadElementException
	 *             on error
	 */
	public static Image getInstance(final int width, final int height, final boolean reverseBits,
			final int typeCCITT, final int parameters, final byte[] data)
			throws BadElementException {
		return Image.getInstance(width, height, reverseBits, typeCCITT,
				parameters, data, null);
	}

	/**
	 * Creates an Image with CCITT G3 or G4 compression. It assumes that the
	 * data bytes are already compressed.
	 *
	 * @param width
	 *            the exact width of the image
	 * @param height
	 *            the exact height of the image
	 * @param reverseBits
	 *            reverses the bits in <code>data</code>. Bit 0 is swapped
	 *            with bit 7 and so on
	 * @param typeCCITT
	 *            the type of compression in <code>data</code>. It can be
	 *            CCITTG4, CCITTG31D, CCITTG32D
	 * @param parameters
	 *            parameters associated with this stream. Possible values are
	 *            CCITT_BLACKIS1, CCITT_ENCODEDBYTEALIGN, CCITT_ENDOFLINE and
	 *            CCITT_ENDOFBLOCK or a combination of them
	 * @param data
	 *            the image data
	 * @param transparency
	 *            transparency information in the Mask format of the image
	 *            dictionary
	 * @return an Image object
	 * @throws BadElementException
	 *             on error
	 */
	public static Image getInstance(final int width, final int height, final boolean reverseBits,
			final int typeCCITT, final int parameters, final byte[] data, final int transparency[])
			throws BadElementException {
		if (transparency != null && transparency.length != 2)
			throw new BadElementException(MessageLocalization.getComposedMessage("transparency.length.must.be.equal.to.2.with.ccitt.images"));
		Image img = new ImgCCITT(width, height, reverseBits, typeCCITT,
				parameters, data);
		img.transparency = transparency;
		return img;
	}

	/**
	 * Gets an instance of an Image in raw mode.
	 *
	 * @param width
	 *            the width of the image in pixels
	 * @param height
	 *            the height of the image in pixels
	 * @param components
	 *            1,3 or 4 for GrayScale, RGB and CMYK
	 * @param data
	 *            the image data
	 * @param bpc
	 *            bits per component
	 * @param transparency
	 *            transparency information in the Mask format of the image
	 *            dictionary
	 * @return an object of type <CODE>ImgRaw</CODE>
	 * @throws BadElementException
	 *             on error
	 */
	public static Image getInstance(final int width, final int height, final int components,
			final int bpc, final byte data[], final int transparency[])
			throws BadElementException {
		if (transparency != null && transparency.length != components * 2)
			throw new BadElementException(MessageLocalization.getComposedMessage("transparency.length.must.be.equal.to.componentes.2"));
		if (components == 1 && bpc == 1) {
			byte g4[] = CCITTG4Encoder.compress(data, width, height);
			return Image.getInstance(width, height, false, Image.CCITTG4,
					Image.CCITT_BLACKIS1, g4, transparency);
		}
		Image img = new ImgRaw(width, height, components, bpc, data);
		img.transparency = transparency;
		return img;
	}

	// images from a PdfTemplate

	/**
	 * gets an instance of an Image
	 *
	 * @param template
	 *            a PdfTemplate that has to be wrapped in an Image object
	 * @return an Image object
	 * @throws BadElementException
	 */
	public static Image getInstance(final PdfTemplate template)
			throws BadElementException {
		return new ImgTemplate(template);
	}

    // image from indirect reference

    /**
     * Holds value of property directReference.
     * An image is embedded into a PDF as an Image XObject.
     * This object is referenced by a PdfIndirectReference object.
     */
    private PdfIndirectReference directReference;

    /**
     * Getter for property directReference.
     * @return Value of property directReference.
     */
    public PdfIndirectReference getDirectReference() {
        return this.directReference;
    }

    /**
     * Setter for property directReference.
     * @param directReference New value of property directReference.
     */
    public void setDirectReference(final PdfIndirectReference directReference) {
        this.directReference = directReference;
    }

    /**
     * Reuses an existing image.
     * @param ref the reference to the image dictionary
     * @throws BadElementException on error
     * @return the image
     */
    public static Image getInstance(final PRIndirectReference ref) throws BadElementException {
        PdfDictionary dic = (PdfDictionary)PdfReader.getPdfObjectRelease(ref);
        int width = ((PdfNumber)PdfReader.getPdfObjectRelease(dic.get(PdfName.WIDTH))).intValue();
        int height = ((PdfNumber)PdfReader.getPdfObjectRelease(dic.get(PdfName.HEIGHT))).intValue();
        Image imask = null;
        PdfObject obj = dic.get(PdfName.SMASK);
        if (obj != null && obj.isIndirect()) {
            imask = getInstance((PRIndirectReference)obj);
        }
        else {
            obj = dic.get(PdfName.MASK);
            if (obj != null && obj.isIndirect()) {
                PdfObject obj2 = PdfReader.getPdfObjectRelease(obj);
                if (obj2 instanceof PdfDictionary)
                    imask = getInstance((PRIndirectReference)obj);
            }
        }
        Image img = new ImgRaw(width, height, 1, 1, null);
        img.imageMask = imask;
        img.directReference = ref;
        return img;
    }

    // copy constructor

	/**
	 * Constructs an <CODE>Image</CODE> -object, using an <VAR>url </VAR>.
	 *
	 * @param image
	 *            another Image object.
	 */
	protected Image(final Image image) {
		super(image);
		this.type = image.type;
		this.url = image.url;
		this.rawData = image.rawData;
		this.bpc = image.bpc;
		this.template = image.template;
		this.alignment = image.alignment;
		this.alt = image.alt;
		this.absoluteX = image.absoluteX;
		this.absoluteY = image.absoluteY;
		this.plainWidth = image.plainWidth;
		this.plainHeight = image.plainHeight;
		this.scaledWidth = image.scaledWidth;
		this.scaledHeight = image.scaledHeight;
		this.mySerialId = image.mySerialId;

        this.directReference = image.directReference;

		this.rotationRadians = image.rotationRadians;
        this.initialRotation = image.initialRotation;
        this.indentationLeft = image.indentationLeft;
        this.indentationRight = image.indentationRight;
		this.spacingBefore = image.spacingBefore;
		this.spacingAfter = image.spacingAfter;

		this.widthPercentage = image.widthPercentage;
		this.scaleToFitLineWhenOverflow = image.scaleToFitLineWhenOverflow;
		this.scaleToFitHeight = image.scaleToFitHeight;
		this.annotation = image.annotation;
		this.layer = image.layer;
		this.interpolation = image.interpolation;
		this.originalType = image.originalType;
		this.originalData = image.originalData;
		this.deflated = image.deflated;
		this.dpiX = image.dpiX;
		this.dpiY = image.dpiY;
		this.XYRatio = image.XYRatio;

		this.colorspace = image.colorspace;
		this.invert = image.invert;
		this.profile = image.profile;
		this.additional = image.additional;
		this.mask = image.mask;
		this.imageMask = image.imageMask;
		this.smask = image.smask;
		this.transparency = image.transparency;
        this.role = image.role;
        if (image.accessibleAttributes != null)
            this.accessibleAttributes = new HashMap<PdfName, PdfObject>(image.accessibleAttributes);
        setId(image.getId());
	}

	/**
	 * gets an instance of an Image
	 *
	 * @param image
	 *            an Image object
	 * @return a new Image object
	 */
	public static Image getInstance(final Image image) {
		if (image == null)
			return null;
		try {
			Class<? extends Image> cs = image.getClass();
			Constructor<? extends Image> constructor = cs
					.getDeclaredConstructor(new Class[] { Image.class });
			return constructor.newInstance(new Object[] { image });
		} catch (Exception e) {
			throw new ExceptionConverter(e);
		}
	}

	// implementation of the Element interface

	/**
	 * Returns the type.
	 *
	 * @return a type
	 */

	@Override
    public int type() {
		return type;
	}

	/**
	 * @see com.itextpdf.text.Element#isNestable()
	 * @since	iText 2.0.8
	 */
	@Override
    public boolean isNestable() {
		return true;
	}

	// checking the type of Image

	/**
	 * Returns <CODE>true</CODE> if the image is a <CODE>Jpeg</CODE>
	 * -object.
	 *
	 * @return a <CODE>boolean</CODE>
	 */

	public boolean isJpeg() {
		return type == JPEG;
	}

	/**
	 * Returns <CODE>true</CODE> if the image is a <CODE>ImgRaw</CODE>
	 * -object.
	 *
	 * @return a <CODE>boolean</CODE>
	 */

	public boolean isImgRaw() {
		return type == IMGRAW;
	}

	/**
	 * Returns <CODE>true</CODE> if the image is an <CODE>ImgTemplate</CODE>
	 * -object.
	 *
	 * @return a <CODE>boolean</CODE>
	 */

	public boolean isImgTemplate() {
		return type == IMGTEMPLATE;
	}

	// getters and setters

	/**
	 * Gets the <CODE>String</CODE> -representation of the reference to the
	 * image.
	 *
	 * @return a <CODE>String</CODE>
	 */

	public URL getUrl() {
		return url;
	}

	/**
	 * Sets the url of the image
	 *
	 * @param url
	 *            the url of the image
	 */
	public void setUrl(final URL url) {
		this.url = url;
	}

	/**
	 * Gets the raw data for the image.
	 * <P>
	 * Remark: this only makes sense for Images of the type <CODE>RawImage
	 * </CODE>.
	 *
	 * @return the raw data
	 */
	public byte[] getRawData() {
		return rawData;
	}

	/**
	 * Gets the bpc for the image.
	 * <P>
	 * Remark: this only makes sense for Images of the type <CODE>RawImage
	 * </CODE>.
	 *
	 * @return a bpc value
	 */
	public int getBpc() {
		return bpc;
	}

	/**
	 * Gets the template to be used as an image.
	 * <P>
	 * Remark: this only makes sense for Images of the type <CODE>ImgTemplate
	 * </CODE>.
	 *
	 * @return the template
	 */
	public PdfTemplate getTemplateData() {
		return template[0];
	}

	/**
	 * Sets data from a PdfTemplate
	 *
	 * @param template
	 *            the template with the content
	 */
	public void setTemplateData(final PdfTemplate template) {
		this.template[0] = template;
	}

	/**
	 * Gets the alignment for the image.
	 *
	 * @return a value
	 */
	public int getAlignment() {
		return alignment;
	}

	/**
	 * Sets the alignment for the image.
	 *
	 * @param alignment
	 *            the alignment
	 */

	public void setAlignment(final int alignment) {
		this.alignment = alignment;
	}

	/**
	 * Gets the alternative text for the image.
	 *
	 * @return a <CODE>String</CODE>
	 */

	public String getAlt() {
		return alt;
	}

	/**
	 * Sets the alternative information for the image.
	 *
	 * @param alt
	 *            the alternative information
	 */

	public void setAlt(final String alt) {
		this.alt = alt;
        setAccessibleAttribute(PdfName.ALT, new PdfString(alt));
	}

	/**
	 * Sets the absolute position of the <CODE>Image</CODE>.
	 *
	 * @param absoluteX
	 * @param absoluteY
	 */

	public void setAbsolutePosition(final float absoluteX, final float absoluteY) {
		this.absoluteX = absoluteX;
		this.absoluteY = absoluteY;
	}

	/**
	 * Checks if the <CODE>Images</CODE> has to be added at an absolute X
	 * position.
	 *
	 * @return a boolean
	 */
	public boolean hasAbsoluteX() {
		return !Float.isNaN(absoluteX);
	}

	/**
	 * Returns the absolute X position.
	 *
	 * @return a position
	 */
	public float getAbsoluteX() {
		return absoluteX;
	}

	/**
	 * Checks if the <CODE>Images</CODE> has to be added at an absolute
	 * position.
	 *
	 * @return a boolean
	 */
	public boolean hasAbsoluteY() {
		return !Float.isNaN(absoluteY);
	}

	/**
	 * Returns the absolute Y position.
	 *
	 * @return a position
	 */
	public float getAbsoluteY() {
		return absoluteY;
	}

	// width and height

	/**
	 * Gets the scaled width of the image.
	 *
	 * @return a value
	 */
	public float getScaledWidth() {
		return scaledWidth;
	}

	/**
	 * Gets the scaled height of the image.
	 *
	 * @return a value
	 */
	public float getScaledHeight() {
		return scaledHeight;
	}

	/**
	 * Gets the plain width of the image.
	 *
	 * @return a value
	 */
	public float getPlainWidth() {
		return plainWidth;
	}

	/**
	 * Gets the plain height of the image.
	 *
	 * @return a value
	 */
	public float getPlainHeight() {
		return plainHeight;
	}

    /**
     * Scale the image to the dimensions of the rectangle
     *
     * @param rectangle dimensions to scale the Image
     */
    public void scaleAbsolute(final Rectangle rectangle) {
        scaleAbsolute(rectangle.getWidth(), rectangle.getHeight());
    }

	/**
	 * Scale the image to an absolute width and an absolute height.
	 *
	 * @param newWidth
	 *            the new width
	 * @param newHeight
	 *            the new height
	 */
	public void scaleAbsolute(final float newWidth, final float newHeight) {
		plainWidth = newWidth;
		plainHeight = newHeight;
		float[] matrix = matrix();
		scaledWidth = matrix[DX] - matrix[CX];
		scaledHeight = matrix[DY] - matrix[CY];
		setWidthPercentage(0);
	}

	/**
	 * Scale the image to an absolute width.
	 *
	 * @param newWidth
	 *            the new width
	 */
	public void scaleAbsoluteWidth(final float newWidth) {
		plainWidth = newWidth;
		float[] matrix = matrix();
		scaledWidth = matrix[DX] - matrix[CX];
		scaledHeight = matrix[DY] - matrix[CY];
		setWidthPercentage(0);
	}

	/**
	 * Scale the image to an absolute height.
	 *
	 * @param newHeight
	 *            the new height
	 */
	public void scaleAbsoluteHeight(final float newHeight) {
		plainHeight = newHeight;
		float[] matrix = matrix();
		scaledWidth = matrix[DX] - matrix[CX];
		scaledHeight = matrix[DY] - matrix[CY];
		setWidthPercentage(0);
	}

	/**
	 * Scale the image to a certain percentage.
	 *
	 * @param percent
	 *            the scaling percentage
	 */
	public void scalePercent(final float percent) {
		scalePercent(percent, percent);
	}

	/**
	 * Scale the width and height of an image to a certain percentage.
	 *
	 * @param percentX
	 *            the scaling percentage of the width
	 * @param percentY
	 *            the scaling percentage of the height
	 */
	public void scalePercent(final float percentX, final float percentY) {
		plainWidth = getWidth() * percentX / 100f;
		plainHeight = getHeight() * percentY / 100f;
		float[] matrix = matrix();
		scaledWidth = matrix[DX] - matrix[CX];
		scaledHeight = matrix[DY] - matrix[CY];
		setWidthPercentage(0);
	}

    /**
     * Scales the images to the dimensions of the rectangle.
     *
     * @param rectangle the dimensions to fit
     */
    public void scaleToFit(final Rectangle rectangle) {
        scaleToFit(rectangle.getWidth(), rectangle.getHeight());
    }

	/**
	 * Scales the image so that it fits a certain width and height.
	 *
	 * @param fitWidth
	 *            the width to fit
	 * @param fitHeight
	 *            the height to fit
	 */
	public void scaleToFit(final float fitWidth, final float fitHeight) {
        scalePercent(100);
		float percentX = fitWidth * 100 / getScaledWidth();
		float percentY = fitHeight * 100 / getScaledHeight();
		scalePercent(percentX < percentY ? percentX : percentY);
		setWidthPercentage(0);
	}


	/**
	 * Returns the transformation matrix of the image.
	 *
	 * @return an array [AX, AY, BX, BY, CX, CY, DX, DY]
	 */
	public float[] matrix() {
		return matrix(1);
	}

	/**
	 * Returns the transformation matrix of the image.
	 *
	 * @return an array [AX, AY, BX, BY, CX, CY, DX, DY]
	 */
	public float[] matrix(float scalePercentage) {
		float[] matrix = new float[8];
		float cosX = (float) Math.cos(rotationRadians);
		float sinX = (float) Math.sin(rotationRadians);
		matrix[AX] = plainWidth * cosX * scalePercentage;
		matrix[AY] = plainWidth * sinX * scalePercentage;
		matrix[BX] = -plainHeight * sinX * scalePercentage;
		matrix[BY] = plainHeight * cosX * scalePercentage;
		if (rotationRadians < Math.PI / 2f) {
			matrix[CX] = matrix[BX];
			matrix[CY] = 0;
			matrix[DX] = matrix[AX];
			matrix[DY] = matrix[AY] + matrix[BY];
		} else if (rotationRadians < Math.PI) {
			matrix[CX] = matrix[AX] + matrix[BX];
			matrix[CY] = matrix[BY];
			matrix[DX] = 0;
			matrix[DY] = matrix[AY];
		} else if (rotationRadians < Math.PI * 1.5f) {
			matrix[CX] = matrix[AX];
			matrix[CY] = matrix[AY] + matrix[BY];
			matrix[DX] = matrix[BX];
			matrix[DY] = 0;
		} else {
			matrix[CX] = 0;
			matrix[CY] = matrix[AY];
			matrix[DX] = matrix[AX] + matrix[BX];
			matrix[DY] = matrix[BY];
		}
		return matrix;
	}

	// serial stamping

	/** a static that is used for attributing a unique id to each image. */
	static long serialId = 0;

	/** Creates a new serial id.
	 * @return the new serialId */
	static protected synchronized Long getSerialId() {
		++serialId;
		return Long.valueOf(serialId);
	}

	/**
	 * Returns a serial id for the Image (reuse the same image more than once)
	 *
	 * @return a serialId
	 */
	public Long getMySerialId() {
		return mySerialId;
	}

    // rotation, note that the superclass also has a rotation value.

	/** This is the rotation of the image in radians. */
	protected float rotationRadians;

    /** Holds value of property initialRotation. */
    private float initialRotation;

    /**
     * Gets the current image rotation in radians.
     * @return the current image rotation in radians
     */
    public float getImageRotation() {
		double d = 2.0 * Math.PI;
		float rot = (float) ((rotationRadians - initialRotation) % d);
		if (rot < 0) {
			rot += d;
		}
        return rot;
    }

	/**
	 * Sets the rotation of the image in radians.
	 *
	 * @param r
	 *            rotation in radians
	 */
	public void setRotation(final float r) {
		double d = 2.0 * Math.PI;
		rotationRadians = (float) ((r + initialRotation) % d);
		if (rotationRadians < 0) {
			rotationRadians += d;
		}
		float[] matrix = matrix();
		scaledWidth = matrix[DX] - matrix[CX];
		scaledHeight = matrix[DY] - matrix[CY];
	}

	/**
	 * Sets the rotation of the image in degrees.
	 *
	 * @param deg
	 *            rotation in degrees
	 */
	public void setRotationDegrees(final float deg) {
		double d = Math.PI;
		setRotation(deg / 180 * (float) d);
	}

    /**
     * Getter for property initialRotation.
     * @return Value of property initialRotation.
     */
    public float getInitialRotation() {
        return this.initialRotation;
    }

    /**
     * Some image formats, like TIFF may present the images rotated that have
     * to be compensated.
     * @param initialRotation New value of property initialRotation.
     */
    public void setInitialRotation(final float initialRotation) {
        float old_rot = rotationRadians - this.initialRotation;
        this.initialRotation = initialRotation;
        setRotation(old_rot);
    }

    // indentations

	/** the indentation to the left. */
	protected float indentationLeft = 0;

	/** the indentation to the right. */
	protected float indentationRight = 0;

	/** The spacing before the image. */
	protected float spacingBefore;

	/** The spacing after the image. */
	protected float spacingAfter;

	/** Padding top */
	protected float paddingTop;

	/**
	 * Gets the left indentation.
	 *
	 * @return the left indentation
	 */
	public float getIndentationLeft() {
		return indentationLeft;
	}

	/**
	 * Sets the left indentation.
	 *
	 * @param f
	 */
	public void setIndentationLeft(final float f) {
		indentationLeft = f;
	}

	/**
	 * Gets the right indentation.
	 *
	 * @return the right indentation
	 */
	public float getIndentationRight() {
		return indentationRight;
	}

	/**
	 * Sets the right indentation.
	 *
	 * @param f
	 */
	public void setIndentationRight(final float f) {
		indentationRight = f;
	}

	/**
	 * Gets the spacing before this image.
	 *
	 * @return the spacing
	 */
	public float getSpacingBefore() {
		return spacingBefore;
	}

	/**
	 * Sets the spacing before this image.
	 *
	 * @param spacing
	 *            the new spacing
	 */

	public void setSpacingBefore(final float spacing) {
		this.spacingBefore = spacing;
	}

	/**
	 * Gets the spacing before this image.
	 *
	 * @return the spacing
	 */
	public float getSpacingAfter() {
		return spacingAfter;
	}

	/**
	 * Sets the spacing after this image.
	 *
	 * @param spacing
	 *            the new spacing
	 */

	public void setSpacingAfter(final float spacing) {
		this.spacingAfter = spacing;
	}

	public float getPaddingTop() {
		return paddingTop;
	}

	public void setPaddingTop(float paddingTop) {
		this.paddingTop = paddingTop;
	}

    // widthpercentage (for the moment only used in ColumnText)

	/**
	 * Holds value of property widthPercentage.
	 */
	private float widthPercentage = 100;

	/**
	 * Getter for property widthPercentage.
	 *
	 * @return Value of property widthPercentage.
	 */
	public float getWidthPercentage() {
		return this.widthPercentage;
	}

	/**
	 * Setter for property widthPercentage.
	 *
	 * @param widthPercentage
	 *            New value of property widthPercentage.
	 */
	public void setWidthPercentage(final float widthPercentage) {
		this.widthPercentage = widthPercentage;
	}

	// scaling the image to the available width (or not)

	/**
	 * Indicates if the image should be scaled to fit the line
	 * when the image exceeds the available width.
	 * @since iText 5.0.6
	 */
	protected boolean scaleToFitLineWhenOverflow;

	/**
	 * Gets the value of scaleToFitLineWhenOverflow.
	 * @return true if the image size has to scale to the available width
	 * @since iText 5.0.6
	 */
	public boolean isScaleToFitLineWhenOverflow() {
		return scaleToFitLineWhenOverflow;
	}

	/**
	 * Sets the value of scaleToFitLineWhenOverflow
	 * @param scaleToFitLineWhenOverflow true if you want the image to scale to the available width
	 * @since iText 5.0.6
	 */
	public void setScaleToFitLineWhenOverflow(final boolean scaleToFitLineWhenOverflow) {
		this.scaleToFitLineWhenOverflow = scaleToFitLineWhenOverflow;
	}

	// scaling the image to the available height (or not)

	/**
	 * Indicates if the image should be scaled to fit
	 * when the image exceeds the available height.
	 * @since iText 5.4.2
	 */
	protected boolean scaleToFitHeight = true;

	/**
	 * Gets the value of scaleToFitHeight.
	 * @return true if the image size has to scale to the available height
	 * @since iText 5.4.2
	 */
	public boolean isScaleToFitHeight() {
		return scaleToFitHeight;
	}

	/**
	 * Sets the value of scaleToFitHeight
	 * @param scaleToFitHeight true if you want the image to scale to the available height
	 * @since iText 5.4.2
	 */
	public void setScaleToFitHeight(final boolean scaleToFitHeight) {
		this.scaleToFitHeight = scaleToFitHeight;
	}

	// annotation

	/** if the annotation is not null the image will be clickable. */
	protected Annotation annotation = null;

	/**
	 * Sets the annotation of this Image.
	 *
	 * @param annotation
	 *            the annotation
	 */
	public void setAnnotation(final Annotation annotation) {
		this.annotation = annotation;
	}

	/**
	 * Gets the annotation.
	 *
	 * @return the annotation that is linked to this image
	 */
	public Annotation getAnnotation() {
		return annotation;
	}

    // Optional Content

    /** Optional Content layer to which we want this Image to belong. */
	protected PdfOCG layer;

	/**
	 * Gets the layer this image belongs to.
	 *
	 * @return the layer this image belongs to or <code>null</code> for no
	 *         layer defined
	 */
	public PdfOCG getLayer() {
		return layer;
	}

	/**
	 * Sets the layer this image belongs to.
	 *
	 * @param layer
	 *            the layer this image belongs to
	 */
	public void setLayer(final PdfOCG layer) {
		this.layer = layer;
	}

	// interpolation

	/** Holds value of property interpolation. */
	protected boolean interpolation;

	/**
	 * Getter for property interpolation.
	 *
	 * @return Value of property interpolation.
	 */
	public boolean isInterpolation() {
		return interpolation;
	}

	/**
	 * Sets the image interpolation. Image interpolation attempts to produce a
	 * smooth transition between adjacent sample values.
	 *
	 * @param interpolation
	 *            New value of property interpolation.
	 */
	public void setInterpolation(final boolean interpolation) {
		this.interpolation = interpolation;
	}

	// original type and data

	/** Holds value of property originalType. */
	protected int originalType = ORIGINAL_NONE;

	/** Holds value of property originalData. */
	protected byte[] originalData;

	/**
	 * Getter for property originalType.
	 *
	 * @return Value of property originalType.
	 *
	 */
	public int getOriginalType() {
		return this.originalType;
	}

	/**
	 * Setter for property originalType.
	 *
	 * @param originalType
	 *            New value of property originalType.
	 *
	 */
	public void setOriginalType(final int originalType) {
		this.originalType = originalType;
	}

	/**
	 * Getter for property originalData.
	 *
	 * @return Value of property originalData.
	 *
	 */
	public byte[] getOriginalData() {
		return this.originalData;
	}

	/**
	 * Setter for property originalData.
	 *
	 * @param originalData
	 *            New value of property originalData.
	 *
	 */
	public void setOriginalData(final byte[] originalData) {
		this.originalData = originalData;
	}

	// the following values are only set for specific types of images.

	/** Holds value of property deflated. */
	protected boolean deflated = false;

	/**
	 * Getter for property deflated.
	 *
	 * @return Value of property deflated.
	 *
	 */
	public boolean isDeflated() {
		return this.deflated;
	}

	/**
	 * Setter for property deflated.
	 *
	 * @param deflated
	 *            New value of property deflated.
	 */
	public void setDeflated(final boolean deflated) {
		this.deflated = deflated;
	}

	// DPI info

	/** Holds value of property dpiX. */
	protected int dpiX = 0;

	/** Holds value of property dpiY. */
	protected int dpiY = 0;

	/**
	 * Gets the dots-per-inch in the X direction. Returns 0 if not available.
	 *
	 * @return the dots-per-inch in the X direction
	 */
	public int getDpiX() {
		return dpiX;
	}

	/**
	 * Gets the dots-per-inch in the Y direction. Returns 0 if not available.
	 *
	 * @return the dots-per-inch in the Y direction
	 */
	public int getDpiY() {
		return dpiY;
	}

	/**
	 * Sets the dots per inch value
	 *
	 * @param dpiX
	 *            dpi for x coordinates
	 * @param dpiY
	 *            dpi for y coordinates
	 */
	public void setDpi(final int dpiX, final int dpiY) {
		this.dpiX = dpiX;
		this.dpiY = dpiY;
	}

	// XY Ratio

	/** Holds value of property XYRatio. */
	private float XYRatio = 0;

	/**
	 * Gets the X/Y pixel dimensionless aspect ratio.
	 *
	 * @return the X/Y pixel dimensionless aspect ratio
	 */
	public float getXYRatio() {
		return this.XYRatio;
	}

	/**
	 * Sets the X/Y pixel dimensionless aspect ratio.
	 *
	 * @param XYRatio
	 *            the X/Y pixel dimensionless aspect ratio
	 */
	public void setXYRatio(final float XYRatio) {
		this.XYRatio = XYRatio;
	}

	// color, colorspaces and transparency

	/** this is the colorspace of a jpeg-image. */
	protected int colorspace = -1;

	/**
	 * Gets the colorspace for the image.
	 * <P>
	 * Remark: this only makes sense for Images of the type <CODE>Jpeg</CODE>.
	 *
	 * @return a colorspace value
	 */
	public int getColorspace() {
		return colorspace;
	}
	
	protected int colortransform = 1;

    public void setColorTransform(int c) {
        colortransform = c;
    }

    public int getColorTransform() {
	    return colortransform;
	}
	
	/** Image color inversion */
	protected boolean invert = false;

	/**
	 * Getter for the inverted value
	 *
	 * @return true if the image is inverted
	 */
	public boolean isInverted() {
		return invert;
	}

	/**
	 * Sets inverted true or false
	 *
	 * @param invert
	 *            true or false
	 */
	public void setInverted(final boolean invert) {
		this.invert = invert;
	}

	/** ICC Profile attached */
	protected ICC_Profile profile = null;

	/**
	 * Tags this image with an ICC profile.
	 *
	 * @param profile
	 *            the profile
	 */
	public void tagICC(final ICC_Profile profile) {
		this.profile = profile;
	}

	/**
	 * Checks is the image has an ICC profile.
	 *
	 * @return the ICC profile or <CODE>null</CODE>
	 */
	public boolean hasICCProfile() {
		return this.profile != null;
	}

	/**
	 * Gets the images ICC profile.
	 *
	 * @return the ICC profile
	 */
	public ICC_Profile getICCProfile() {
		return profile;
	}

	/** a dictionary with additional information */
	private PdfDictionary additional = null;

	/**
	 * Getter for the dictionary with additional information.
	 *
	 * @return a PdfDictionary with additional information.
	 */
	public PdfDictionary getAdditional() {
		return this.additional;
	}

	/**
	 * Sets the /Colorspace key.
	 *
	 * @param additional
	 *            a PdfDictionary with additional information.
	 */
	public void setAdditional(final PdfDictionary additional) {
		this.additional = additional;
	}

    /**
     * Replaces CalRGB and CalGray colorspaces with DeviceRGB and DeviceGray.
     */
    public void simplifyColorspace() {
        if (additional == null)
            return;
        PdfArray value = additional.getAsArray(PdfName.COLORSPACE);
        if (value == null)
            return;
        PdfObject cs = simplifyColorspace(value);
        PdfObject newValue;
        if (cs.isName())
            newValue = cs;
        else {
            newValue = value;
            PdfName first = value.getAsName(0);
            if (PdfName.INDEXED.equals(first)) {
                if (value.size() >= 2) {
                    PdfArray second = value.getAsArray(1);
                    if (second != null) {
                        value.set(1, simplifyColorspace(second));
                    }
                }
            }
        }
        additional.put(PdfName.COLORSPACE, newValue);
    }

	/**
	 * Gets a PDF Name from an array or returns the object that was passed.
	 */
    private PdfObject simplifyColorspace(final PdfArray obj) {
        if (obj == null)
            return obj;
        PdfName first = obj.getAsName(0);
        if (PdfName.CALGRAY.equals(first))
            return PdfName.DEVICEGRAY;
        else if (PdfName.CALRGB.equals(first))
            return PdfName.DEVICERGB;
        else
            return obj;
    }

	/** Is this image a mask? */
	protected boolean mask = false;

	/** The image that serves as a mask for this image. */
	protected Image imageMask;

	/** Holds value of property smask. */
	private boolean smask;

	/**
	 * Returns <CODE>true</CODE> if this <CODE>Image</CODE> is a mask.
	 *
	 * @return <CODE>true</CODE> if this <CODE>Image</CODE> is a mask
	 */
	public boolean isMask() {
		return mask;
	}

	/**
	 * Make this <CODE>Image</CODE> a mask.
	 *
	 * @throws DocumentException
	 *             if this <CODE>Image</CODE> can not be a mask
	 */
	public void makeMask() throws DocumentException {
		if (!isMaskCandidate())
			throw new DocumentException(MessageLocalization.getComposedMessage("this.image.can.not.be.an.image.mask"));
		mask = true;
	}

	/**
	 * Returns <CODE>true</CODE> if this <CODE>Image</CODE> has the
	 * requisites to be a mask.
	 *
	 * @return <CODE>true</CODE> if this <CODE>Image</CODE> can be a mask
	 */
	public boolean isMaskCandidate() {
		if (type == IMGRAW) {
			if (bpc > 0xff)
				return true;
		}
		return colorspace == 1;
	}

	/**
	 * Gets the explicit masking.
	 *
	 * @return the explicit masking
	 */
	public Image getImageMask() {
		return imageMask;
	}

	/**
	 * Sets the explicit masking.
	 *
	 * @param mask
	 *            the mask to be applied
	 * @throws DocumentException
	 *             on error
	 */
	public void setImageMask(final Image mask) throws DocumentException {
		if (this.mask)
			throw new DocumentException(MessageLocalization.getComposedMessage("an.image.mask.cannot.contain.another.image.mask"));
		if (!mask.mask)
			throw new DocumentException(MessageLocalization.getComposedMessage("the.image.mask.is.not.a.mask.did.you.do.makemask"));
		imageMask = mask;
		smask = mask.bpc > 1 && mask.bpc <= 8;
	}

	/**
	 * Getter for property smask.
	 *
	 * @return Value of property smask.
	 *
	 */
	public boolean isSmask() {
		return this.smask;
	}

	/**
	 * Setter for property smask.
	 *
	 * @param smask
	 *            New value of property smask.
	 */
	public void setSmask(final boolean smask) {
		this.smask = smask;
	}

	/** this is the transparency information of the raw image */
	protected int transparency[];

	/**
	 * Returns the transparency.
	 *
	 * @return the transparency values
	 */

	public int[] getTransparency() {
		return transparency;
	}

	/**
	 * Sets the transparency values
	 *
	 * @param transparency
	 *            the transparency values
	 */
	public void setTransparency(final int transparency[]) {
		this.transparency = transparency;
	}


	/**
	 * Returns the compression level used for images written as a compressed stream.
	 * @return the compression level (0 = best speed, 9 = best compression, -1 is default)
     * @since	2.1.3
	 */
	public int getCompressionLevel() {
		return compressionLevel;
	}

	/**
	 * Sets the compression level to be used if the image is written as a compressed stream.
	 * @param compressionLevel a value between 0 (best speed) and 9 (best compression)
     * @since	2.1.3
	 */
	public void setCompressionLevel(final int compressionLevel) {
		if (compressionLevel < PdfStream.NO_COMPRESSION || compressionLevel > PdfStream.BEST_COMPRESSION)
			this.compressionLevel = PdfStream.DEFAULT_COMPRESSION;
		else
			this.compressionLevel = compressionLevel;
	}

	public PdfObject getAccessibleAttribute(final PdfName key) {
        if (accessibleAttributes != null)
            return accessibleAttributes.get(key);
        else
            return null;
    }

    public void setAccessibleAttribute(final PdfName key, final PdfObject value) {
        if (accessibleAttributes == null)
            accessibleAttributes = new HashMap<PdfName, PdfObject>();
        accessibleAttributes.put(key, value);
    }

    public HashMap<PdfName, PdfObject> getAccessibleAttributes() {
        return accessibleAttributes;
    }

    public PdfName getRole() {
        return role;
    }

    public void setRole(final PdfName role) {
        this.role = role;
    }

    public AccessibleElementId getId() {
        if (id == null)
            id = new AccessibleElementId();
        return id;
    }

    public void setId(final AccessibleElementId id) {
        this.id = id;
    }

    public boolean isInline() {
        return true;
    }

    // AWT related methods (remove this if you port to Android / GAE)

	/**
	 * Gets an instance of an Image from a java.awt.Image.
	 *
	 * @param image
	 *            the <CODE>java.awt.Image</CODE> to convert
	 * @param color
	 *            if different from <CODE>null</CODE> the transparency pixels
	 *            are replaced by this color
	 * @param forceBW
	 *            if <CODE>true</CODE> the image is treated as black and white
	 * @return an object of type <CODE>ImgRaw</CODE>
	 * @throws BadElementException
	 *             on error
	 * @throws IOException
	 *             on error
	 */
	public static Image getInstance(final java.awt.Image image, final java.awt.Color color,
			boolean forceBW) throws BadElementException, IOException {

		if(image instanceof java.awt.image.BufferedImage){
			java.awt.image.BufferedImage bi = (java.awt.image.BufferedImage) image;
			if(bi.getType() == java.awt.image.BufferedImage.TYPE_BYTE_BINARY
				&& bi.getColorModel().getPixelSize() == 1) {
				forceBW=true;
			}
		}

		java.awt.image.PixelGrabber pg = new java.awt.image.PixelGrabber(image,
				0, 0, -1, -1, true);
		try {
			pg.grabPixels();
		} catch (InterruptedException e) {
			throw new IOException(MessageLocalization.getComposedMessage("java.awt.image.interrupted.waiting.for.pixels"));
		}
		if ((pg.getStatus() & java.awt.image.ImageObserver.ABORT) != 0) {
			throw new IOException(MessageLocalization.getComposedMessage("java.awt.image.fetch.aborted.or.errored"));
		}
		int w = pg.getWidth();
		int h = pg.getHeight();
		int[] pixels = (int[]) pg.getPixels();
		if (forceBW) {
			int byteWidth = w / 8 + ((w & 7) != 0 ? 1 : 0);
			byte[] pixelsByte = new byte[byteWidth * h];

			int index = 0;
			int size = h * w;
			int transColor = 1;
			if (color != null) {
				transColor = color.getRed() + color.getGreen()
						+ color.getBlue() < 384 ? 0 : 1;
			}
			int transparency[] = null;
			int cbyte = 0x80;
			int wMarker = 0;
			int currByte = 0;
			if (color != null) {
				for (int j = 0; j < size; j++) {
					int alpha = pixels[j] >> 24 & 0xff;
					if (alpha < 250) {
						if (transColor == 1)
							currByte |= cbyte;
					} else {
						if ((pixels[j] & 0x888) != 0)
							currByte |= cbyte;
					}
					cbyte >>= 1;
					if (cbyte == 0 || wMarker + 1 >= w) {
						pixelsByte[index++] = (byte) currByte;
						cbyte = 0x80;
						currByte = 0;
					}
					++wMarker;
					if (wMarker >= w)
						wMarker = 0;
				}
			} else {
				for (int j = 0; j < size; j++) {
					if (transparency == null) {
						int alpha = pixels[j] >> 24 & 0xff;
						if (alpha == 0) {
							transparency = new int[2];
							/* bugfix by M.P. Liston, ASC, was: ... ? 1: 0; */
							transparency[0] = transparency[1] = (pixels[j] & 0x888) != 0 ? 0xff : 0;
						}
					}
					if ((pixels[j] & 0x888) != 0)
						currByte |= cbyte;
					cbyte >>= 1;
					if (cbyte == 0 || wMarker + 1 >= w) {
						pixelsByte[index++] = (byte) currByte;
						cbyte = 0x80;
						currByte = 0;
					}
					++wMarker;
					if (wMarker >= w)
						wMarker = 0;
				}
			}
			return Image.getInstance(w, h, 1, 1, pixelsByte, transparency);
		} else {
			byte[] pixelsByte = new byte[w * h * 3];
			byte[] smask = null;

			int index = 0;
			int size = h * w;
			int red = 255;
			int green = 255;
			int blue = 255;
			if (color != null) {
				red = color.getRed();
				green = color.getGreen();
				blue = color.getBlue();
			}
			int transparency[] = null;
			if (color != null) {
				for (int j = 0; j < size; j++) {
					int alpha = pixels[j] >> 24 & 0xff;
					if (alpha < 250) {
						pixelsByte[index++] = (byte) red;
						pixelsByte[index++] = (byte) green;
						pixelsByte[index++] = (byte) blue;
					} else {
						pixelsByte[index++] = (byte) (pixels[j] >> 16 & 0xff);
						pixelsByte[index++] = (byte) (pixels[j] >> 8 & 0xff);
						pixelsByte[index++] = (byte) (pixels[j] & 0xff);
					}
				}
			} else {
				int transparentPixel = 0;
				smask = new byte[w * h];
				boolean shades = false;
				for (int j = 0; j < size; j++) {
					byte alpha = smask[j] = (byte) (pixels[j] >> 24 & 0xff);
					/* bugfix by Chris Nokleberg */
					if (!shades) {
						if (alpha != 0 && alpha != -1) {
							shades = true;
						} else if (transparency == null) {
							if (alpha == 0) {
								transparentPixel = pixels[j] & 0xffffff;
								transparency = new int[6];
								transparency[0] = transparency[1] = transparentPixel >> 16 & 0xff;
								transparency[2] = transparency[3] = transparentPixel >> 8 & 0xff;
								transparency[4] = transparency[5] = transparentPixel & 0xff;
								// Added by Michael Klink
								// Check whether this value for transparent pixels
								// has already been used for a non-transparent one
								// before this position
								for (int prevPixel = 0; prevPixel < j; prevPixel++) {
									if ((pixels[prevPixel] & 0xffffff) == transparentPixel) {
										// found a prior use of the transparentPixel color
										// and, therefore, cannot make use of this color
										// for transparency; we could still use an image
										// mask but for simplicity let's use a soft mask
										// which already is implemented here
										shades = true;
										break;
									}
								}
							}
						} else if (((pixels[j] & 0xffffff) != transparentPixel) && (alpha == 0)) {
							shades = true;
						} else if (((pixels[j] & 0xffffff) == transparentPixel) && (alpha != 0)) {
							shades = true;
						}
					}
					pixelsByte[index++] = (byte) (pixels[j] >> 16 & 0xff);
					pixelsByte[index++] = (byte) (pixels[j] >> 8 & 0xff);
					pixelsByte[index++] = (byte) (pixels[j] & 0xff);
				}
				if (shades)
					transparency = null;
				else
					smask = null;
			}
			Image img = Image.getInstance(w, h, 3, 8, pixelsByte, transparency);
			if (smask != null) {
				Image sm = Image.getInstance(w, h, 1, 8, smask);
				try {
					sm.makeMask();
					img.setImageMask(sm);
				} catch (DocumentException de) {
					throw new ExceptionConverter(de);
				}
			}
			return img;
		}
	}

	/**
	 * Gets an instance of an Image from a java.awt.Image.
	 *
	 * @param image
	 *            the <CODE>java.awt.Image</CODE> to convert
	 * @param color
	 *            if different from <CODE>null</CODE> the transparency pixels
	 *            are replaced by this color
	 * @return an object of type <CODE>ImgRaw</CODE>
	 * @throws BadElementException
	 *             on error
	 * @throws IOException
	 *             on error
	 */
	public static Image getInstance(final java.awt.Image image, final java.awt.Color color)
			throws BadElementException, IOException {
		return Image.getInstance(image, color, false);
	}

	/**
	 * Gets an instance of a Image from a java.awt.Image.
	 * The image is added as a JPEG with a user defined quality.
	 *
	 * @param writer
	 *            the <CODE>PdfWriter</CODE> object to which the image will be added
	 * @param awtImage
	 *            the <CODE>java.awt.Image</CODE> to convert
	 * @param quality
	 *            a float value between 0 and 1
	 * @return an object of type <CODE>PdfTemplate</CODE>
	 * @throws BadElementException
	 *             on error
	 * @throws IOException
	 */
	public static Image getInstance(final PdfWriter writer, final java.awt.Image awtImage, final float quality) throws BadElementException, IOException {
		return getInstance(new PdfContentByte(writer), awtImage, quality);
	}

    /**
     * Gets an instance of a Image from a java.awt.Image.
     * The image is added as a JPEG with a user defined quality.
     *
     * @param cb
     *            the <CODE>PdfContentByte</CODE> object to which the image will be added
     * @param awtImage
     *            the <CODE>java.awt.Image</CODE> to convert
     * @param quality
     *            a float value between 0 and 1
     * @return an object of type <CODE>PdfTemplate</CODE>
     * @throws BadElementException
     *             on error
     * @throws IOException
     */
    public static Image getInstance(final PdfContentByte cb, final java.awt.Image awtImage, final float quality) throws BadElementException, IOException {
        java.awt.image.PixelGrabber pg = new java.awt.image.PixelGrabber(awtImage,
                0, 0, -1, -1, true);
        try {
            pg.grabPixels();
        } catch (InterruptedException e) {
            throw new IOException(MessageLocalization.getComposedMessage("java.awt.image.interrupted.waiting.for.pixels"));
        }
        if ((pg.getStatus() & java.awt.image.ImageObserver.ABORT) != 0) {
            throw new IOException(MessageLocalization.getComposedMessage("java.awt.image.fetch.aborted.or.errored"));
        }
        int w = pg.getWidth();
        int h = pg.getHeight();
        PdfTemplate tp = cb.createTemplate(w, h);
        PdfGraphics2D g2d = new PdfGraphics2D(tp, w, h, null, false, true, quality);
        g2d.drawImage(awtImage, 0, 0, null);
        g2d.dispose();
        return getInstance(tp);
    }
}
