GetImageBytesTest.java

/*
    This file is part of the iText (R) project.
    Copyright (c) 1998-2025 Apryse Group NV
    Authors: Apryse Software.

    This program is offered under a commercial and under the AGPL license.
    For commercial licensing, contact us at https://itextpdf.com/sales.  For AGPL licensing, see below.

    AGPL licensing:
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    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 <https://www.gnu.org/licenses/>.
 */
package com.itextpdf.kernel.pdf.xobject;

import com.itextpdf.commons.utils.FileUtil;
import com.itextpdf.commons.utils.MessageFormatUtil;
import com.itextpdf.io.codec.TIFFConstants;
import com.itextpdf.io.codec.TIFFDirectory;
import com.itextpdf.io.codec.TIFFField;
import com.itextpdf.io.exceptions.IoExceptionMessageConstant;
import com.itextpdf.io.source.RandomAccessFileOrArray;
import com.itextpdf.io.source.RandomAccessSourceFactory;
import com.itextpdf.kernel.exceptions.KernelExceptionMessageConstant;
import com.itextpdf.kernel.pdf.PdfDictionary;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfIndirectReference;
import com.itextpdf.kernel.pdf.PdfName;
import com.itextpdf.kernel.pdf.PdfObject;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.PdfResources;
import com.itextpdf.kernel.pdf.PdfStream;
import com.itextpdf.kernel.pdf.canvas.parser.EventType;
import com.itextpdf.kernel.pdf.canvas.parser.PdfCanvasProcessor;
import com.itextpdf.kernel.pdf.canvas.parser.data.IEventData;
import com.itextpdf.kernel.pdf.canvas.parser.data.ImageRenderInfo;
import com.itextpdf.kernel.pdf.canvas.parser.listener.IEventListener;
import com.itextpdf.test.ExtendedITextTest;
import com.itextpdf.test.TestUtil;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Set;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;


@Tag("IntegrationTest")
public class GetImageBytesTest extends ExtendedITextTest {

    private static final String SOURCE_FOLDER = "./src/test/resources/com/itextpdf/kernel/pdf/xobject"
            + "/GetImageBytesTest/";
    private static final String DESTINATION_FOLDER = TestUtil.getOutputPath() + "/kernel/pdf/xobject/GetImageBytesTest/";

    @BeforeAll
    public static void beforeClass() {
        createOrClearDestinationFolder(DESTINATION_FOLDER);
    }

    @Test
    public void testMultiStageFilters() throws Exception {
        // TODO DEVSIX-2940: extracted image is blank
        testFile("multistagefilter1.pdf", "Obj13", "jpg");
    }

    @Test
    // Android-Conversion-Ignore-Test (TODO DEVSIX-6445 fix different DeflaterOutputStream behavior)
    public void testAscii85Filters() throws Exception {
        testFile("ASCII85_RunLengthDecode.pdf", "Im9", "png");
    }

    @Test
    // Android-Conversion-Ignore-Test (TODO DEVSIX-6445 fix different DeflaterOutputStream behavior)
    public void testCcittFilters() throws Exception {
        testFile("ccittfaxdecode.pdf", "background0", "png");
    }

    @Test
    // Android-Conversion-Ignore-Test (TODO DEVSIX-6445 fix different DeflaterOutputStream behavior)
    public void testFlateDecodeFilters() throws Exception {
        // TODO DEVSIX-2941: extracted indexed devicegray RunLengthDecode gets color inverted
        testFile("flatedecode_runlengthdecode.pdf", "Im9", "png");
    }

    @Test
    public void testDctDecodeFilters() throws Exception {
        // TODO DEVSIX-2940: extracted image is upside down
        testFile("dctdecode.pdf", "im1", "jpg");
    }

    @Test
    public void testjbig2Filters() throws Exception {
        // TODO DEVSIX-2942: extracted jbig2 image is not readable by most popular image viewers
        testFile("jbig2decode.pdf", "2", "jbig2");
    }

    @Test
    public void testFlateCmyk() throws Exception {
        testFile("img_cmyk.pdf", "Im1", "tif");
    }

    @Test
    public void testFlateCmykIcc() throws Exception {
        testFile("img_cmyk_icc.pdf", "Im1", "tif");
    }

    @Test
    public void testFlateIndexed() throws Exception {
        testFile("img_indexed.pdf", "Im1", "png");
    }

    @Test
    // Android-Conversion-Ignore-Test (TODO DEVSIX-6445 fix different DeflaterOutputStream behavior)
    public void testFlateRgbIcc() throws Exception {
        testFile("img_rgb_icc.pdf", "Im1", "png");
    }

    @Test
    public void testFlateRgb() throws Exception {
        testFile("img_rgb.pdf", "Im1", "png");
    }

    @Test
    // Android-Conversion-Ignore-Test (TODO DEVSIX-6445 fix different DeflaterOutputStream behavior)
    public void testFlateCalRgb() throws Exception {
        testFile("img_calrgb.pdf", "Im1", "png");
    }

    @Test
    public void testJPXDecode() throws Exception {
        testFile("JPXDecode.pdf", "Im1", "jp2");
    }

    @Test
    public void testSeparationCSWithICCBasedAsAlternative() throws Exception {
        testFile("separationCSWithICCBasedAsAlternative.pdf", "Im1", "png");
    }

    @Test
    // Android-Conversion-Ignore-Test (TODO DEVSIX-6445 fix different DeflaterOutputStream behavior)
    public void testSeparationCSWithDeviceCMYKAsAlternative() throws Exception {
        Assertions.assertThrows(UnsupportedOperationException.class, () ->
        {
            testFile("separationCSWithDeviceCMYKAsAlternative.pdf", "Im1", "png");
        });
    }

    @Test
    public void testGrayScalePng() throws Exception {
        testFile("grayImages.pdf", "Im1", "png");
    }

    @Test
    // Android-Conversion-Ignore-Test (TODO DEVSIX-6445 fix different DeflaterOutputStream behavior)
    public void testSeparationCSWithDeviceRGBAsAlternative() throws Exception {
        testFile("separationCSWithDeviceRgbAsAlternative.pdf", "Im1", "png");
    }

    @Test
    // Android-Conversion-Ignore-Test (TODO DEVSIX-6445 fix different DeflaterOutputStream behavior)
    public void testSeparationCSWithDeviceRGBAsAlternative2() throws Exception {
        testFile("spotColorImagesSmall.pdf", "Im1", "png");
    }

    @Test
    public void testRGBSeparationCSWithJPXDecoderAndFunctionType0() throws Exception {
        testFile("RGBJpxF0.pdf", "Im1", "jp2");
    }

    @Test
    public void testRGBSeparationCSWithDCTDecoderAndFunctionType0() throws Exception {
        testFile("RGBDctF0.pdf", "Im1", "jpg");
    }

    @Test
    // Android-Conversion-Ignore-Test (TODO DEVSIX-6445 fix different DeflaterOutputStream behavior)
    public void testRGBSeparationCSWithFlateDecoderAndFunctionType0() throws Exception {
        testFile("RGBFlateF0.pdf", "Im1", "png");
    }

    @Test
    public void testCMYKSeparationCSWithJPXDecoderAndFunctionType2() throws Exception {
        testFile("CMYKJpxF2.pdf", "Im1", "jp2");
    }

    @Test
    public void testRGBSeparationCSWithJPXDecoderAndFunctionType2() throws Exception {
        testFile("RGBJpxF2.pdf", "Im1", "jp2");
    }

    @Test
    public void testCMYKSeparationCSWithDCTDecoderAndFunctionType2() throws Exception {
        testFile("CMYKDctF2.pdf", "Im1", "jpg");
    }

    @Test
    public void testRGBSeparationCSWithDCTDecoderAndFunctionType2() throws Exception {
        testFile("RGBDctF2.pdf", "Im1", "jpg");
    }

    @Test
    public void testRGBSeparationCSWithFlateDecoderAndFunctionType2() throws Exception {
        testFile("RGBFlateF2.pdf", "Im1", "png");
    }

    @Test
    public void extractByteAlignedG4TiffImageTest() throws IOException {
        String inFileName = SOURCE_FOLDER + "extractByteAlignedG4TiffImage.pdf";
        String outImageFileName = DESTINATION_FOLDER + "extractedByteAlignedImage.png";
        String cmpImageFileName = SOURCE_FOLDER + "cmp_extractByteAlignedG4TiffImage.png";

        PdfDocument pdfDocument = new PdfDocument(new PdfReader(inFileName));

        ImageExtractor listener = new ImageExtractor();
        PdfCanvasProcessor processor = new PdfCanvasProcessor(listener);
        processor.processPageContent(pdfDocument.getPage(1));

        java.util.List<byte[]> images = listener.getImages();
        Assertions.assertEquals(1, images.size());

        try (OutputStream fos = FileUtil.getFileOutputStream(outImageFileName)) {
            fos.write(images.get(0), 0, images.size());
        }

        // expected and actual are swapped here for simplicity
        int expectedLen = images.get(0).length;
        byte[] buf = new byte[expectedLen];
        try (InputStream is = FileUtil.getInputStreamForFile(cmpImageFileName)) {
            int read = is.read(buf, 0, buf.length);
            Assertions.assertEquals(expectedLen, read);
            read = is.read(buf, 0, buf.length);
            Assertions.assertTrue(read <= 0);
        }
        Assertions.assertArrayEquals(images.get(0), buf);
    }

    @Test
    public void expectedByteAlignedTiffImageExtractionTest() throws IOException {
        //Byte-aligned image is expected in pdf file, but in fact it's not
        String inFileName = SOURCE_FOLDER + "expectedByteAlignedTiffImageExtraction.pdf";

        PdfDocument pdfDocument = new PdfDocument(new PdfReader(inFileName));

        ImageExtractor listener = new ImageExtractor();
        PdfCanvasProcessor processor = new PdfCanvasProcessor(listener);

        Exception e = Assertions.assertThrows(com.itextpdf.io.exceptions.IOException.class,
                () -> processor.processPageContent(pdfDocument.getPage(1))
        );
        Assertions.assertEquals(MessageFormatUtil
                        .format(IoExceptionMessageConstant.EXPECTED_TRAILING_ZERO_BITS_FOR_BYTE_ALIGNED_LINES),
                e.getMessage());
    }

    @Test
    public void deviceGray8bitTest() throws Exception {
        testFile("deviceGray8bit.pdf", "fzImg0", "png");
    }

    @Test
    public void deviceGray8bitFlateDecodeTest() throws Exception {
        testFile("deviceGray8bitFlateDecode.pdf", "Im1", "png");
    }

    @Test
    public void deviceGray1bitFlateDecodeInvertedTest() throws Exception {
        testFile("deviceGray1bitFlateDecodeInverted.pdf", "Im0", "png", true);
    }

    @Test
    //TODO DEVSIX-7015 Support decoding images with Decode array and BitsPerComponent more than 1
    public void deviceGray4bitFlateDecodeInvertedTest() throws Exception {
        testFile("deviceGray4bitFlateDecodeInverted.pdf", "Im1", "png");
    }

    @Test
    public void deviceGray8bitFlateDecodeWithMaskTest() throws Exception {
        testFile("deviceGray8bitFlateDecodeWithMask.pdf", "Im1", "png");
    }

    @Test
    public void deviceGray8bitDctDecodeTest() throws Exception {
        testFile("deviceGray8bitDctDecode.pdf", "fzImg0", "jpg");
    }

    @Test
    public void deviceGray8bitJPXDecodeTest() throws Exception {
        testFile("deviceGray8bitJPXDecode.pdf", "fzImg0", "jp2");
    }

    @Test
    public void deviceGray1bitCCITTFaxDecodeTest() throws Exception {
        testFile("deviceGray1bitCCITTFaxDecode.pdf", "Im1", "png");
    }

    @Test
    public void deviceGray8bitFlateDecodeMaskRotatedTest() throws Exception {
        testFile("deviceGray8bitFlateDecodeMaskRotated.pdf", "Im1", "png");
    }

    @Test
    public void deviceGray8bitFlateDecodeScaledTest() throws Exception {
        testFile("deviceGray8bitFlateDecodeScaled.pdf", "Im1", "png");
    }

    @Test
    public void deviceGray8bitFlateCombinedTransformationTest() throws Exception {
        testFile("deviceGray8bitFlateCombinedTransformation.pdf", "Im1", "png");
    }

    @Test
    public void dRgb1BitDecodeInvertTest() {
        Exception e = Assertions.assertThrows(com.itextpdf.io.exceptions.IOException.class, () -> testFile("dRgb1BitDecodeInvert.pdf", "Im1", "tif"));
        Assertions.assertEquals(MessageFormatUtil.format(IoExceptionMessageConstant.COLOR_DEPTH_IS_NOT_SUPPORTED,
                1), e.getMessage());
    }

    @Test
    public void dRgb1BitDecodeTest() {
        Exception e = Assertions.assertThrows(com.itextpdf.io.exceptions.IOException.class, () -> testFile("dRgb1BitDecode.pdf", "Im1", "tif"));
        Assertions.assertEquals(MessageFormatUtil.format(IoExceptionMessageConstant.COLOR_DEPTH_IS_NOT_SUPPORTED,
                1), e.getMessage());
    }

    @Test
    public void dRgb1BitTest() {
        Exception e = Assertions.assertThrows(com.itextpdf.io.exceptions.IOException.class, () -> testFile("dRgb1Bit.pdf", "Im1", "tif"));
        Assertions.assertEquals(MessageFormatUtil.format(IoExceptionMessageConstant.COLOR_DEPTH_IS_NOT_SUPPORTED,
                1), e.getMessage());
    }

    @Test
    public void dRgb4BitDecodeInvertTest() {
        Exception e = Assertions.assertThrows(com.itextpdf.io.exceptions.IOException.class, () -> testFile("dRgb4BitDecodeInvert.pdf", "Im1", "tif"));
        Assertions.assertEquals(MessageFormatUtil.format(IoExceptionMessageConstant.COLOR_DEPTH_IS_NOT_SUPPORTED,
                4), e.getMessage());
    }

    @Test
    public void dRgb4BitDecodeTest() {
        Exception e = Assertions.assertThrows(com.itextpdf.io.exceptions.IOException.class, () -> testFile("dRgb4BitDecode.pdf", "Im1", "tif"));
        Assertions.assertEquals(MessageFormatUtil.format(IoExceptionMessageConstant.COLOR_DEPTH_IS_NOT_SUPPORTED,
                4), e.getMessage());
    }

    @Test
    public void dRgb4BitTest() {
        Exception e = Assertions.assertThrows(com.itextpdf.io.exceptions.IOException.class, () -> testFile("dRgb4Bit.pdf", "Im1", "tif"));
        Assertions.assertEquals(MessageFormatUtil.format(IoExceptionMessageConstant.COLOR_DEPTH_IS_NOT_SUPPORTED,
                4), e.getMessage());
    }

    @Test
    public void dRgbDctDecodeInvertTest() throws Exception {
        testFile("dRgbDctDecodeInvert.pdf", "Im1", "jpg");
    }

    @Test
    public void dRgbDctDecodeTest() throws Exception {
        testFile("dRgbDctDecode.pdf", "Im1", "jpg");
    }

    @Test
    public void dRgbDctMaskedTest() throws Exception {
        testFile("dRgbDctMasked.pdf", "Im1", "jpg");
    }

    @Test
    public void dRgbDCTMaskedDecodeTest() throws Exception {
        testFile("dRgbDCTMaskedDecode.pdf", "Im1", "jpg");
    }

    @Test
    public void dRgbDCTMaskedInvertTest() throws Exception {
        testFile("dRgbDCTMaskedInvert.pdf", "Im1", "jpg");
    }

    @Test
    public void dRgbDCTtransformationsDecodeInvertTest() throws Exception {
        testFile("dRgbDCTtransformationsDecodeInvert.pdf", "Im1", "jpg");
    }

    @Test
    public void dRgbDCTtransformationsDecodeTest() throws Exception {
        testFile("dRgbDCTtransformationsDecode.pdf", "Im1", "jpg");
    }

    @Test
    public void dRgbDCTtransformationsMaskedDecodeInvertTest() throws Exception {
        testFile("dRgbDCTtransformationsMaskedDecodeInvert.pdf", "Im1", "jpg");
    }

    @Test
    public void dRgbDCTtransformationsMaskedDecodeTest() throws Exception {
        testFile("dRgbDCTtransformationsMaskedDecode.pdf", "Im1", "jpg");
    }

    @Test
    public void dRgbDCTtransformationsTest() throws Exception {
        testFile("dRgbDCTtransformations.pdf", "Im1", "jpg");
    }

    @Test
    public void dRgbDCTTransparancyDecodeInvertTest() throws Exception {
        testFile("dRgbDCTTransparancyDecodeInvert.pdf", "Im1", "jpg");
    }

    @Test
    public void dRgbDCTTransparancyDecodeTest() throws Exception {
        testFile("dRgbDCTTransparancyDecode.pdf", "Im1", "jpg");
    }

    @Test
    public void dRgbDCTTransparancyMaskDecodeInvertTest() throws Exception {
        testFile("dRgbDCTTransparancyMaskDecodeInvert.pdf", "Im1", "jpg");
    }

    @Test
    public void dRgbDCTTransparancyMaskDecodeTest() throws Exception {
        testFile("dRgbDCTTransparancyMaskDecode.pdf", "Im1", "jpg");
    }

    @Test
    public void dRgbDCTTransparancyMaskTest() throws Exception {
        testFile("dRgbDCTTransparancyMask.pdf", "Im1", "jpg");
    }

    @Test
    public void dRgbDCTTransparancyTest() throws Exception {
        testFile("dRgbDCTTransparancy.pdf", "Im1", "jpg");
    }

    @Test
    public void dRgbDCTTransparancyTransformDecodeInvertTest() throws Exception {
        testFile("dRgbDCTTransparancyTransformDecodeInvert.pdf", "Im1", "jpg");
    }

    @Test
    public void dRgbDCTTransparancyTransformDecodeTest() throws Exception {
        testFile("dRgbDCTTransparancyTransformDecode.pdf", "Im1", "jpg");
    }

    @Test
    public void dRgbDCTTransparancyTransformMaskDecodeInvertTest() throws Exception {
        testFile("dRgbDCTTransparancyTransformMaskDecodeInvert.pdf", "Im1", "jpg");
    }

    @Test
    public void dRgbDCTTransparancyTransformMaskDecodeTest() throws Exception {
        testFile("dRgbDCTTransparancyTransformMaskDecode.pdf", "Im1", "jpg");
    }

    @Test
    public void dRgbDCTTransparancyTransformMaskTest() throws Exception {
        testFile("dRgbDCTTransparancyTransformMask.pdf", "Im1", "jpg");
    }

    @Test
    public void dRgbDCTTransparancyTransformTest() throws Exception {
        testFile("dRgbDCTTransparancyTransform.pdf", "Im1", "jpg");
    }

    @Test
    public void dRgbFlateTest() throws Exception {
        testFile("dRgbFlate.pdf", "Im0", "png");
    }

    @Test
    public void dRgbFlateTransparencyTest() throws Exception {
        testFile("dRgbFlateTransparency.pdf", "Im0", "png");
    }

    @Test
    public void dRgbFlateInvertedTest() throws Exception {
        testFile("dRgbFlateInverted.pdf", "Im0", "png");
    }

    @Test
    public void dRgbFlateRotatedTest() throws Exception {
        testFile("dRgbFlateRotated.pdf", "Im0", "png");
    }

    @Test
    public void dRgbFlateRotatedInvertedTest() throws Exception {
        testFile("dRgbFlateRotatedInverted.pdf", "Im0", "png");
    }

    @Test
    public void dRgbFlate1bitTest() {
        Exception e = Assertions.assertThrows(com.itextpdf.io.exceptions.IOException.class,
                () -> testFile("dRgbFlate1bit.pdf", "Im0", "tif"));
        Assertions.assertEquals(MessageFormatUtil.format(IoExceptionMessageConstant.COLOR_DEPTH_IS_NOT_SUPPORTED,
                1), e.getMessage());
    }

    @Test
    public void dRgbFlate4bitTest() {
        Exception e = Assertions.assertThrows(com.itextpdf.io.exceptions.IOException.class,
                () -> testFile("dRgbFlate4bit.pdf", "Im0", "tif"));
        Assertions.assertEquals(MessageFormatUtil.format(IoExceptionMessageConstant.COLOR_DEPTH_IS_NOT_SUPPORTED,
                4), e.getMessage());
    }
    
    @Test
    public void ICCBasedDctMaskedInvertedTest() throws Exception {
        testFile("ICCBasedDctMaskedInverted.pdf", "Im1", "jpg");
    }

    @Test
    public void ICCBasedDCTTransformMaskedDecodeTest() throws Exception {
        testFile("ICCBasedDCTTransformMaskedDecode.pdf", "Im1", "jpg");
    }

    @Test
    public void ICCBasedDCTTransformMaskedDecodeInvertTest() throws Exception {
        testFile("ICCBasedDCTTransformMaskedDecodeInvert.pdf", "Im1", "jpg");
    }

    @Test
    public void ICCBasedFlateTransformMaskedDecodeTest() throws Exception {
        testFile("ICCBasedFlateTransformMaskedDecode.pdf", "Im1", "jpg");
    }

    @Test
    public void ICCBasedFlateTransformMaskedDecodeInvertTest() throws Exception {
        testFile("ICCBasedFlateTransformMaskedDecodeInvert.pdf", "Im1", "jpg");
    }

    @Test
    public void deviceCMYKTest() throws Exception {
        testFile("deviceCMYK.pdf", "Im1", "tif");
    }

    @Test
    public void deviceCMYKFlateDecodeInvertedTest() throws Exception {
        testFile("deviceCMYKFlateDecodeInverted.pdf", "Im1", "tif");
    }

    @Test
    public void calGray8bitTest() throws Exception {
        testFile("calGray8bit.pdf", "Im1", "png");
    }

    @Test
    public void calGray8bitGamma22Test() throws Exception {
        testFile("calGray8bitGamma22.pdf", "Im1", "png");
    }

    @Test
    public void calGray8bitGamma18InvertedTest() throws Exception {
        testFile("calGray8bitGamma18Inverted.pdf", "Im1", "png");
    }

    @Test
    public void calGray1bitTest() throws Exception {
        testFile("calGray1bit.pdf", "Im1", "png");
    }

    @Test
    public void calGray1bitInvertedTest() throws Exception {
        testFile("calGray1bitInverted.pdf", "Im1", "png");
    }

    @Test
    public void calGray4bitGamma22Test() throws Exception {
        testFile("calGray4bitGamma22.pdf", "Im1", "png");
    }

    @Test
    public void calGray4bitGamma10InvertedTest() throws Exception {
        testFile("calGray4bitGamma10Inverted.pdf", "Im1", "png");
    }

    @Test
    public void calGray8bitExtGStateTest() throws Exception {
        testFile("calGray8bitExtGStateTest.pdf", "Im1", "png");
    }

    @Test
    public void calRGB8bitTest() throws Exception {
        testFile("calRGB8bit.pdf", "Im1", "png");
    }

    @Test
    public void calRGB8bitCustomGammaTest() throws Exception {
        testFile("calRGB8bitCustomGamma.pdf", "Im1", "png");
    }

    @Test
    public void calRGB8bitInvertedTest() throws Exception {
        testFile("calRGB8bitInverted.pdf", "Im1", "png");
    }

    @Test
    public void calRGB4bitTest() {
        Exception e = Assertions.assertThrows(com.itextpdf.io.exceptions.IOException.class,
                () -> testFile("calRGB4bit.pdf", "Im1", "tif"));
        Assertions.assertEquals(MessageFormatUtil.format(IoExceptionMessageConstant.COLOR_DEPTH_IS_NOT_SUPPORTED,
                "4"), e.getMessage());
    }

    @Test
    public void calRGB8bitNoFilterTest() throws Exception {
        testFile("calRGB8bitNoFilter.pdf", "Im1", "png");
    }

    @Test
    public void calRGB8bitSMaskTest() throws Exception {
        testFile("calRGB8bitSMask.pdf", "Im1", "png");
    }

    @Test
    public void calRGB8bitExtGStateTest() throws Exception {
        testFile("calRGB8bitExtGState.pdf", "Im1", "png");
    }

    @Test
    public void calRGB8bitCustomWhitePointTest() throws Exception {
        testFile("calRGB8bitCustomWhitePoint.pdf", "Im1", "png");
    }

    @Test
    public void calRGB1bitTest() {
        Exception e = Assertions.assertThrows(com.itextpdf.io.exceptions.IOException.class,
                () -> testFile("calRGB1bit.pdf", "Im1", "tif"));
        Assertions.assertEquals(MessageFormatUtil.format(IoExceptionMessageConstant.COLOR_DEPTH_IS_NOT_SUPPORTED,
                "1"), e.getMessage());
    }

    @Test
    public void calRGB2bitTest() {
        Exception e = Assertions.assertThrows(com.itextpdf.io.exceptions.IOException.class,
                () -> testFile("calRGB2bit.pdf", "Im1", "tif"));
        Assertions.assertEquals(MessageFormatUtil.format(IoExceptionMessageConstant.COLOR_DEPTH_IS_NOT_SUPPORTED,
                "2"), e.getMessage());
    }

    @Test
    public void lab8bitTest() {
        Exception e = Assertions.assertThrows(com.itextpdf.io.exceptions.IOException.class,
                () -> testFile("lab8bit.pdf", "Im1", "tif"));
        Assertions.assertEquals(MessageFormatUtil.format(IoExceptionMessageConstant.COLOR_SPACE_IS_NOT_SUPPORTED,
                "/Lab"), e.getMessage());
    }

    @Test
    public void labDctMaskedTest() throws Exception {
        testFile("labDctMasked.pdf", "Im1", "jpg");
    }

    @Test
    public void labDctTransformTest() throws Exception {
        testFile("labDctTransform.pdf", "Im1", "jpg");
    }

    @Test
    public void labDctTransparancyTest() throws Exception {
        testFile("labDctTransparancy.pdf", "Im1", "jpg");
    }

    @Test
    public void labDctTransparancyMaskTest() throws Exception {
        testFile("labDctTransparancyMask.pdf", "Im1", "jpg");
    }

    @Test
    public void labDctTransparancyTransformTest() throws Exception {
        testFile("labDctTransparancyTransform.pdf", "Im1", "jpg");
    }

    @Test
    public void labDctTransparancyTransformMaskTest() throws Exception {
        testFile("labDctTransparancyTransformMask.pdf", "Im1", "jpg");
    }

    @Test
    public void indexed1bitTest() throws Exception {
        testFile("indexed1bit.pdf", "Im0", "png");
    }

    @Test
    public void indexed2bitTest() throws Exception {
        testFile("indexed2bit.pdf", "Im0", "png");
    }

    @Test
    public void indexed4bitTest() throws Exception {
        testFile("indexed4bit.pdf", "Im0", "png");
    }

    @Test
    public void indexed8bitTest() throws Exception {
        testFile("indexed8bit.pdf", "Im0", "png");
    }

    @Test
    public void indexed8bitGradientTest() throws Exception {
        testFile("indexed8bitGradient.pdf", "Im0", "png");
    }

    @Test
    public void indexed8bitSMaskTest() throws Exception {
        testFile("indexed8bitSMask.pdf", "Im0", "png");
    }

    @Test
    public void separation1bitDeviceCMYKTest() {
        Exception e = Assertions.assertThrows(UnsupportedOperationException.class,
                () -> testFile("separation1bitDeviceCMYK.pdf", "Im0", "png"));
        Assertions.assertEquals(KernelExceptionMessageConstant.GET_IMAGEBYTES_FOR_SEPARATION_COLOR_ONLY_SUPPORTS_RGB,
                e.getMessage());

    }

    @Test
    public void separation8bitDeviceCMYKTest() {
        Exception e = Assertions.assertThrows(UnsupportedOperationException.class,
                () -> testFile("separation8bitDeviceCMYK.pdf", "Im0", "png"));
        Assertions.assertEquals(KernelExceptionMessageConstant.GET_IMAGEBYTES_FOR_SEPARATION_COLOR_ONLY_SUPPORTS_RGB,
                e.getMessage());
    }

    @Test
    public void separation8bitDeviceRGBTest() throws Exception {
        testFile("separation8bitDeviceRGB.pdf", "Im0", "png");
    }

    @Test
    public void separation8bitLabTest() throws Exception {
        testFile("separation8bitLab.pdf", "Im0", "png");
    }

    @Test
    public void separation8bitDeviceCMYKExtGStateTest() {
        Exception e = Assertions.assertThrows(UnsupportedOperationException.class,
                () -> testFile("separation8bitDeviceCMYKExtGState.pdf", "Im0", "png"));
        Assertions.assertEquals(KernelExceptionMessageConstant.GET_IMAGEBYTES_FOR_SEPARATION_COLOR_ONLY_SUPPORTS_RGB,
                e.getMessage());
    }

    @Test
    public void separation8bitDeviceRGBTransparencyTest() throws Exception {
        testFile("separation8bitDeviceRGBTransparency.pdf", "Im0", "png");
    }

    @Test
    public void separation8bitDeviceRGBDctDecodeTest() throws Exception {
        testFile("separation8bitDeviceRGBDctDecode.pdf", "Im0", "jpg");
    }

    @Test
    public void separation8bitDeviceRGBCustomDecodeRangeTest() throws Exception {
        testFile("separation8bitDeviceRGBCustomDecodeRange.pdf", "Im0", "png");
    }

    @Test
    public void separation1bitDeviceRGBTest() throws Exception {
        testFile("separation1bitDeviceRGB.pdf", "Im0", "png");
    }

    @Test
    public void separation2bitDeviceRGBTest() throws Exception {
        testFile("separation2bitDeviceRGB.pdf", "Im0", "png");
    }

    @Test
    public void separation4bitDeviceRGBTest() throws Exception {
        testFile("separation4bitDeviceRGB.pdf", "Im0", "png");
    }

    @Test
    public void deviceN8bitDeviceCMYKTest() {
        Exception e = Assertions.assertThrows(com.itextpdf.io.exceptions.IOException.class,
                () -> testFile("deviceN8bitDeviceCMYK.pdf", "Im0", "tif"));
        Assertions.assertEquals(MessageFormatUtil.format(IoExceptionMessageConstant.COLOR_SPACE_IS_NOT_SUPPORTED,
                "/DeviceN"), e.getMessage());
    }

    @Test
    public void deviceN8bitDeviceRGBTransparencyTest() {
        Exception e = Assertions.assertThrows(com.itextpdf.io.exceptions.IOException.class,
                () -> testFile("deviceN8bitDeviceRGBTransparency.pdf", "Im0", "tif"));
        Assertions.assertEquals(MessageFormatUtil.format(IoExceptionMessageConstant.COLOR_SPACE_IS_NOT_SUPPORTED,
                "/DeviceN"), e.getMessage());
    }

    @Test
    public void deviceN8bitDeviceRGBSpotASpotBTest() {
        Exception e = Assertions.assertThrows(com.itextpdf.io.exceptions.IOException.class,
                () -> testFile("deviceN8bitDeviceRGBSpotASpotB.pdf", "Im0", "tif"));
        Assertions.assertEquals(MessageFormatUtil.format(IoExceptionMessageConstant.COLOR_SPACE_IS_NOT_SUPPORTED,
                "/DeviceN"), e.getMessage());
    }

    @Test
    public void deviceN4bitDeviceCMYKTest() {
        Exception e = Assertions.assertThrows(com.itextpdf.io.exceptions.IOException.class,
                () -> testFile("deviceN4bitDeviceCMYKTest.pdf", "Im0", "tif"));
        Assertions.assertEquals(MessageFormatUtil.format(IoExceptionMessageConstant.COLOR_DEPTH_IS_NOT_SUPPORTED,
                "4"), e.getMessage());
    }

    @Test
    public void deviceN8bitDeviceCMYKTransparencyDCTDecodeTest() throws Exception {
        testFile("deviceN8bitDeviceCMYKTransparencyDCTDecode.pdf", "Im0", "jpg");
    }

    @Test
    public void deviceN8bit5ChannelsTest() {
        Exception e = Assertions.assertThrows(com.itextpdf.io.exceptions.IOException.class,
                () -> testFile("deviceN8bit5Channels.pdf", "Im0", "tif"));
        Assertions.assertEquals(MessageFormatUtil.format(IoExceptionMessageConstant.COLOR_SPACE_IS_NOT_SUPPORTED,
                "/DeviceN"), e.getMessage());
    }

    @Test
    public void deviceN8bitDeviceRGBCustomDecodeTest() {
        Exception e = Assertions.assertThrows(com.itextpdf.io.exceptions.IOException.class,
                () -> testFile("deviceN8bitDeviceRGBCustomDecode.pdf", "Im0", "tif"));
        Assertions.assertEquals(MessageFormatUtil.format(IoExceptionMessageConstant.COLOR_SPACE_IS_NOT_SUPPORTED,
                "/DeviceN"), e.getMessage());
    }

    @Test
    public void deviceN8bitDeviceCMYKFunctionType0Test() {
        Exception e = Assertions.assertThrows(com.itextpdf.io.exceptions.IOException.class,
                () -> testFile("deviceN8bitDeviceCMYKFunctionType0.pdf", "Im0", "tif"));
        Assertions.assertEquals(MessageFormatUtil.format(IoExceptionMessageConstant.COLOR_SPACE_IS_NOT_SUPPORTED,
                "/DeviceN"), e.getMessage());
    }

    @Test
    public void deviceN8bitDeviceRGBRotatedTest() {
        Exception e = Assertions.assertThrows(com.itextpdf.io.exceptions.IOException.class,
                () -> testFile("deviceN8bitDeviceRGBRotated.pdf", "Im0", "tif"));
        Assertions.assertEquals(MessageFormatUtil.format(IoExceptionMessageConstant.COLOR_SPACE_IS_NOT_SUPPORTED,
                "/DeviceN"), e.getMessage());
    }

    private void testFile(String filename, String objectid, String expectedImageFormat) throws Exception {
        testFile(filename, objectid, expectedImageFormat, false);
    }

    private void testFile(String filename, String objectid, String expectedImageFormat, boolean saveResult)
            throws Exception {
        try (PdfReader reader = new PdfReader(SOURCE_FOLDER + filename);
                PdfDocument pdfDocument = new PdfDocument(reader)) {
            PdfResources resources = pdfDocument.getPage(1).getResources();
            PdfDictionary xobjects = resources.getResource(PdfName.XObject);

            PdfImageXObject img = findImageXObjectByName(xobjects, new PdfName(objectid));

            if (img == null) {
                throw new IllegalArgumentException("Image reference " + objectid
                        + " not found - Available keys are " + xobjects.keySet());
            }

            Assertions.assertEquals(expectedImageFormat, img.identifyImageFileExtension());

            byte[] result = img.getImageBytes(true);
            if (saveResult) {
                Files.write(Paths.get(
                                SOURCE_FOLDER,
                                filename.substring(0, filename.length() - 4) + ".new." + expectedImageFormat),
                        result);
            }

            byte[] cmpBytes = Files.readAllBytes(Paths.get(
                    SOURCE_FOLDER, filename.substring(0, filename.length() - 4) + "." + expectedImageFormat));
            if (img.identifyImageFileExtension().equals("tif")) {
                compareTiffImages(cmpBytes, result);
            } else {
                Assertions.assertArrayEquals(cmpBytes, result);
            }
        }
    }


    private PdfImageXObject findImageXObjectByName(PdfDictionary xobjects, PdfName targetName) {
        if (xobjects == null) {
            return null;
        }

        for (PdfName name : xobjects.keySet()) {
            PdfObject obj = xobjects.get(name);
            if (obj == null) continue;

            if (obj.isIndirectReference()) {
                obj = ((PdfIndirectReference) obj).getRefersTo();
            }

            if (!(obj instanceof PdfStream)) continue;
            PdfStream stream = (PdfStream) obj;
            PdfName subtype = stream.getAsName(PdfName.Subtype);

            if (PdfName.Image.equals(subtype) && name.equals(targetName)) {
                return new PdfImageXObject(stream);
            }

            if (PdfName.Form.equals(subtype)) {
                PdfDictionary innerResources = stream.getAsDictionary(PdfName.Resources);
                if (innerResources != null) {
                    PdfDictionary innerXObjects = innerResources.getAsDictionary(PdfName.XObject);
                    PdfImageXObject result = findImageXObjectByName(innerXObjects, targetName);
                    if (result != null) return result;
                }
            }
        }

        return null;
    }

    private void compareTiffImages(byte[] cmpBytes, byte[] resultBytes) throws IOException {
        int cmpNumDirectories = TIFFDirectory.getNumDirectories(new RandomAccessFileOrArray(
                new RandomAccessSourceFactory().createSource(cmpBytes)));
        int resultNumDirectories = TIFFDirectory.getNumDirectories(new RandomAccessFileOrArray(
                new RandomAccessSourceFactory().createSource(resultBytes)));

        Assertions.assertEquals(cmpNumDirectories, resultNumDirectories);

        for (int dirNum = 0; dirNum < cmpNumDirectories; ++dirNum) {
            TIFFDirectory cmpDir = new TIFFDirectory(new RandomAccessFileOrArray(
                    new RandomAccessSourceFactory().createSource(cmpBytes)), dirNum);
            TIFFDirectory resultDir = new TIFFDirectory(new RandomAccessFileOrArray(
                    new RandomAccessSourceFactory().createSource(resultBytes)), dirNum);

            Assertions.assertEquals(cmpDir.getNumEntries(), resultDir.getNumEntries());
            Assertions.assertEquals(cmpDir.getIFDOffset(), resultDir.getIFDOffset());
            Assertions.assertEquals(cmpDir.getNextIFDOffset(), resultDir.getNextIFDOffset());
            Assertions.assertArrayEquals(cmpDir.getTags(), resultDir.getTags());

            for (int tag : cmpDir.getTags()) {
                Assertions.assertEquals(cmpDir.isTagPresent(tag), resultDir.isTagPresent(tag));

                TIFFField cmpField = cmpDir.getField(tag);
                TIFFField resultField = resultDir.getField(tag);

                if (tag != TIFFConstants.TIFFTAG_SOFTWARE) {
                    compareFields(cmpField, resultField);
                }
            }

            compareImageData(cmpDir, resultDir, cmpBytes, resultBytes);
        }
    }

    private void compareFields(TIFFField cmpField, TIFFField resultField) {
        if (cmpField.getType() == TIFFField.TIFF_LONG) {
            Assertions.assertArrayEquals(cmpField.getAsLongs(), resultField.getAsLongs());
        } else if (cmpField.getType() == TIFFField.TIFF_BYTE) {
            Assertions.assertArrayEquals(cmpField.getAsBytes(), resultField.getAsBytes());
        } else if (cmpField.getType() == TIFFField.TIFF_SBYTE) {
            Assertions.assertArrayEquals(cmpField.getAsBytes(), resultField.getAsBytes());
        } else if (cmpField.getType() == TIFFField.TIFF_SHORT) {
            Assertions.assertArrayEquals(cmpField.getAsChars(), resultField.getAsChars());
        } else if (cmpField.getType() == TIFFField.TIFF_SLONG) {
            Assertions.assertArrayEquals(cmpField.getAsInts(), resultField.getAsInts());
        } else if (cmpField.getType() == TIFFField.TIFF_SSHORT) {
            Assertions.assertArrayEquals(cmpField.getAsChars(), resultField.getAsChars());
        } else if (cmpField.getType() == TIFFField.TIFF_UNDEFINED) {
            Assertions.assertArrayEquals(cmpField.getAsBytes(), resultField.getAsBytes());
        } else if (cmpField.getType() == TIFFField.TIFF_DOUBLE) {
            Assertions.assertArrayEquals(cmpField.getAsDoubles(), resultField.getAsDoubles(), 0);
        } else if (cmpField.getType() == TIFFField.TIFF_FLOAT) {
            Assertions.assertArrayEquals(cmpField.getAsFloats(), resultField.getAsFloats(), 0);
        } else if (cmpField.getType() == TIFFField.TIFF_RATIONAL) {
            Assertions.assertArrayEquals(cmpField.getAsRationals(), resultField.getAsRationals());
        } else if (cmpField.getType() == TIFFField.TIFF_SRATIONAL) {
            Assertions.assertArrayEquals(cmpField.getAsSRationals(), resultField.getAsSRationals());
        } else if (cmpField.getType() == TIFFField.TIFF_ASCII) {
            Assertions.assertArrayEquals(cmpField.getAsStrings(), resultField.getAsStrings());
        } else {
            Assertions.assertArrayEquals(cmpField.getAsBytes(), resultField.getAsBytes());
        }
    }

    private void compareImageData(TIFFDirectory cmpDir, TIFFDirectory resultDir, byte[] cmpBytes, byte[] resultBytes) {
        Assertions.assertTrue(cmpDir.isTagPresent(TIFFConstants.TIFFTAG_STRIPOFFSETS));
        Assertions.assertTrue(cmpDir.isTagPresent(TIFFConstants.TIFFTAG_STRIPBYTECOUNTS));
        Assertions.assertTrue(resultDir.isTagPresent(TIFFConstants.TIFFTAG_STRIPOFFSETS));
        Assertions.assertTrue(resultDir.isTagPresent(TIFFConstants.TIFFTAG_STRIPBYTECOUNTS));

        long[] cmpImageOffsets = cmpDir.getField(TIFFConstants.TIFFTAG_STRIPOFFSETS).getAsLongs();
        long[] cmpStripByteCountsArray = cmpDir.getField(TIFFConstants.TIFFTAG_STRIPOFFSETS).getAsLongs();
        long[] resultImageOffsets = resultDir.getField(TIFFConstants.TIFFTAG_STRIPOFFSETS).getAsLongs();
        long[] resultStripByteCountsArray = resultDir.getField(TIFFConstants.TIFFTAG_STRIPOFFSETS).getAsLongs();

        Assertions.assertEquals(cmpImageOffsets.length, resultImageOffsets.length);
        Assertions.assertEquals(cmpStripByteCountsArray.length, resultStripByteCountsArray.length);

        for (int i = 0; i < cmpImageOffsets.length; ++i) {
            int cmpOffset = (int) cmpImageOffsets[i], cmpCounts = (int) cmpStripByteCountsArray[i];
            int resultOffset = (int) resultImageOffsets[i], resultCounts = (int) resultStripByteCountsArray[i];

            Assertions.assertArrayEquals(subArray(cmpBytes, cmpOffset, (cmpOffset + cmpCounts - 1)),
                    subArray(resultBytes, resultOffset, (resultOffset + resultCounts - 1)));
        }
    }

    private byte[] subArray(byte[] array, int beg, int end) {
        return Arrays.copyOfRange(array, beg, end + 1);
    }

    private class ImageExtractor implements IEventListener {
        private final java.util.List<byte[]> images = new ArrayList<>();

        public void eventOccurred(IEventData data, EventType type) {
            switch (type) {
                case RENDER_IMAGE:
                    ImageRenderInfo renderInfo = (ImageRenderInfo) data;
                    byte[] bytes = renderInfo.getImage().getImageBytes();
                    images.add(bytes);
                    break;
                default:
                    break;
            }
        }

        public Set<EventType> getSupportedEvents() {
            return null;
        }

        public java.util.List<byte[]> getImages() {
            return images;
        }
    }
}