Image.java

/*
 * $Id: Image.java 4065 2009-09-16 23:09:11Z psoares33 $
 *
 * Copyright 1999, 2000, 2001, 2002 by Bruno Lowagie.
 *
 * The contents of this file are subject to the Mozilla Public License Version 1.1
 * (the "License"); you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the License.
 *
 * The Original Code is 'iText, a free JAVA-PDF library'.
 *
 * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
 * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
 * All Rights Reserved.
 * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
 * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
 *
 * Contributor(s): all the names of the contributors are added in the source code
 * where applicable.
 *
 * Alternatively, the contents of this file may be used under the terms of the
 * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
 * provisions of LGPL are applicable instead of those above.  If you wish to
 * allow use of your version of this file only under the terms of the LGPL
 * License and not to allow others to use your version of this file under
 * the MPL, indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by the LGPL.
 * If you do not delete the provisions above, a recipient may use your version
 * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the MPL as stated above or under the terms of the GNU
 * Library General Public License as published by the Free Software Foundation;
 * either version 2 of the License, or any later version.
 *
 * This library 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 Library general Public License for more
 * details.
 *
 * If you didn't download this code from the following link, you should check if
 * you aren't using an obsolete version:
 * https://github.com/LibrePDF/OpenPDF
 */

package org.openpdf.text;

import org.openpdf.text.error_messages.MessageLocalization;
import org.openpdf.text.pdf.PdfDictionary;
import org.openpdf.text.pdf.PdfIndirectReference;
import org.openpdf.text.pdf.PdfStream;
import org.openpdf.text.pdf.PdfTemplate;
import java.net.URL;


/**
 * An <CODE>Image</CODE> is the representation of a graphic element (JPEG, PNG or GIF) that has to be inserted into the
 * document
 *
 * Security consideration: This method accepts input and processes it without built-in validation,
 * filtering, or access restrictions. It may load resources such as local files or external URLs,
 * depending on the input. This can introduce security risks including unauthorized file access,
 * directory traversal, or server-side request forgery (SSRF).
 *
 * It is the responsibility of the calling application to validate and sanitize all input
 * before passing it to this method, especially when handling user-controlled or external data.
 * Refer to established secure coding guidelines to ensure safe usage.
 *
 * @see Element
 * @see Rectangle
 */

public abstract class Image extends Rectangle {

    /**
     * Image alignment: Default alignment (same as LEFT).
     * The image will be placed on the left side of the page.
     */
    public static final int DEFAULT = 0;

    /**
     * Image alignment: Right-align the image.
     * The image will be placed on the right side of the page.
     * Can be combined with TEXTWRAP or UNDERLYING using bitwise OR.
     * Example: {@code image.setAlignment(Image.RIGHT | Image.TEXTWRAP);}
     */
    public static final int RIGHT = 2;

    /**
     * Image alignment: Left-align the image.
     * The image will be placed on the left side of the page.
     * Can be combined with TEXTWRAP or UNDERLYING using bitwise OR.
     * Example: {@code image.setAlignment(Image.LEFT | Image.TEXTWRAP);}
     */
    public static final int LEFT = 0;

    /**
     * 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;

    /**
     * a static that is used for attributing a unique id to each image.
     */
    static long serialId = 0;
    /**
     * 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;

    // image from file or URL
    /**
     * an iText attributed unique id for this image.
     */
    protected Long mySerialId = getSerialId();
    /**
     * This is the rotation of the image in radians.
     */
    protected float rotationRadians;
    /**
     * 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;
    /**
     * Holds value of property interpolation.
     */
    protected boolean interpolation;

    // images from a PdfTemplate
    /**
     * Holds value of property originalType.
     */
    protected int originalType = ORIGINAL_NONE;

    // images from a java.awt.Image
    /**
     * Holds value of property originalData.
     */
    protected byte[] originalData;
    /**
     * Holds value of property deflated.
     */
    protected boolean deflated = false;
    /**
     * Holds value of property dpiX.
     */
    protected int dpiX = 0;
    /**
     * Holds value of property dpiY.
     */
    protected int dpiY = 0;

    // image from indirect reference
    /**
     * this is the colorspace of a jpeg-image.
     */
    protected int colorspace = -1;
    /**
     * Image color inversion
     */
    protected boolean invert = false;
    /**
     * ICC Profile attached
     */
    /**
     * Is this image a mask?
     */
    protected boolean mask = false;

    // copy constructor
    /**
     * The image that serves as a mask for this image.
     */
    protected Image imageMask;
    /**
     * this is the transparency information of the raw image
     */
    protected int[] transparency;

    // implementation of the Element interface
    /**
     * 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;
    /**
     * Holds value of property initialRotation.
     */
    private float initialRotation;

    // checking the type of Image
    /**
     * Holds value of property widthPercentage.
     */
    private float widthPercentage = 100;
    /**
     * Holds value of property XYRatio.
     */
    private float XYRatio = 0;
    /**
     * a dictionary with additional information
     */
    private PdfDictionary additional = null;

    // getters and setters
    /**
     * Holds value of property smask.
     */
    private boolean smask;

    /**
     * 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(URL url) {
        super(0, 0);
        this.url = url;
        this.alignment = DEFAULT;
        rotationRadians = 0;
    }

    /**
     * Constructs an <CODE>Image</CODE> -object, using an <VAR>url </VAR>.
     *
     * @param image another Image object.
     */
    protected Image(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.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.additional = image.additional;
        this.mask = image.mask;
        this.imageMask = image.imageMask;
        this.smask = image.smask;
        this.transparency = image.transparency;
    }

    /**
     * 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(int width, int height, int components,
            int bpc, byte[] data) throws BadElementException {
        return Image.getInstance(width, height, components, bpc, data, null);
    }

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

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

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

    public int type() {
        return type;
    }

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

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

    /**
     * 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;
    }

    // serial stamping

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

    /**
     * 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;
    }

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

    /**
     * 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 the alignment for the image.
     * <p>
     * The alignment parameter can be one of the following base values:
     * <ul>
     *   <li>{@link #LEFT} or {@link #DEFAULT} - Align the image to the left</li>
     *   <li>{@link #RIGHT} - Align the image to the right</li>
     *   <li>{@link #MIDDLE} - Center the image horizontally</li>
     * </ul>
     * <p>
     * The base alignment can be combined with modifiers using bitwise OR (|):
     * <ul>
     *   <li>{@link #TEXTWRAP} - Allow text to wrap around the image (useful for inline images)</li>
     *   <li>{@link #UNDERLYING} - Place the image behind text (useful for watermarks, seals, stamps)</li>
     * </ul>
     * <p>
     * Examples:
     * <pre>{@code
     * // Image aligned left with text wrapping around it on the right
     * image.setAlignment(Image.LEFT | Image.TEXTWRAP);
     * 
     * // Image aligned right with text wrapping around it on the left
     * image.setAlignment(Image.RIGHT | Image.TEXTWRAP);
     * 
     * // Seal or stamp that appears behind text on the right
     * image.setAlignment(Image.RIGHT | Image.UNDERLYING);
     * 
     * // Watermark that appears behind centered text
     * image.setAlignment(Image.MIDDLE | Image.UNDERLYING);
     * }</pre>
     *
     * @param alignment the alignment value (can be a combination of flags using bitwise OR)
     */

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

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

    // indentations

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

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

    /**
     * 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;
    }

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

    // annotation

    /**
     * Returns the transformation matrix of the image.
     *
     * @return an array [AX, AY, BX, BY, CX, CY, DX, DY]
     */
    public float[] matrix() {
        float[] matrix = new float[8];
        float cosX = (float) Math.cos(rotationRadians);
        float sinX = (float) Math.sin(rotationRadians);
        matrix[AX] = plainWidth * cosX;
        matrix[AY] = plainWidth * sinX;
        matrix[BX] = (-plainHeight) * sinX;
        matrix[BY] = plainHeight * cosX;
        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;
    }

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

    // Optional Content

    // interpolation

    // original type and data

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

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

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

    // DPI info

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

    // XY Ratio

    // color, colorspaces and transparency

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

    /**
     * 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;
    }

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

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

    /**
     * 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(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;
    }

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

    public int[] getTransparency() {
        return 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(int compressionLevel) {
        if (compressionLevel < PdfStream.NO_COMPRESSION || compressionLevel > PdfStream.BEST_COMPRESSION) {
            this.compressionLevel = PdfStream.DEFAULT_COMPRESSION;
        } else {
            this.compressionLevel = compressionLevel;
        }
    }



    /**
     * 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(int width, int height, int components,
            int bpc, byte[] data, int[] transparency)
            throws BadElementException {
        if (transparency != null && transparency.length != components * 2) {
            throw new BadElementException(
                    MessageLocalization.getComposedMessage("transparency.length.must.be.equal.to.componentes.2"));
        }
        Image img = new ImgRaw(width, height, components, bpc, data);
        img.transparency = transparency;
        return img;
    }
}