CCITTFactoryTest.java

/*
 * Copyright 2014 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.pdfbox.pdmodel.graphics.image;

import java.awt.image.BufferedImage;

import java.io.File;
import java.io.IOException;

import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.Arrays;

import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode;
import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;

import static org.apache.pdfbox.pdmodel.graphics.image.ValidateXImage.checkIdent;
import static org.apache.pdfbox.pdmodel.graphics.image.ValidateXImage.validate;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;

/**
 * Unit tests for CCITTFactory
 *
 * @author Tilman Hausherr
 */
@Execution(ExecutionMode.CONCURRENT)
class CCITTFactoryTest
{
    private static final File TESTRESULTSDIR = new File("target/test-output/graphics");

    @BeforeAll
    static void setUp()
    {
        TESTRESULTSDIR.mkdirs();
    }

    /**
     * Tests CCITTFactory#createFromRandomAccess(PDDocument document,
     * RandomAccess reader) with a single page TIFF
     */
    @Test
    void testCreateFromRandomAccessSingle() throws IOException
    {
        String tiffG3Path = "src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/ccittg3.tif";
        String tiffG4Path = "src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/ccittg4.tif";
        
        try (PDDocument document = new PDDocument())
        {
            PDImageXObject ximage3 = CCITTFactory.createFromFile(document, new File(tiffG3Path));
            validate(ximage3, 1, 344, 287, "tiff", PDDeviceGray.INSTANCE.getName());
            BufferedImage bim3 = ImageIO.read(new File(tiffG3Path));
            checkIdent(bim3, ximage3.getOpaqueImage(null, 1));
            PDPage page = new PDPage(PDRectangle.A4);
            document.addPage(page);
            try (PDPageContentStream contentStream = new PDPageContentStream(document, page, AppendMode.APPEND, false))
            {
                contentStream.drawImage(ximage3, 0, 0, ximage3.getWidth(), ximage3.getHeight());
            }
            
            PDImageXObject ximage4 = CCITTFactory.createFromFile(document, new File(tiffG4Path));
            validate(ximage4, 1, 344, 287, "tiff", PDDeviceGray.INSTANCE.getName());
            BufferedImage bim4 = ImageIO.read(new File(tiffG3Path));
            checkIdent(bim4, ximage4.getOpaqueImage(null, 1));
            page = new PDPage(PDRectangle.A4);
            document.addPage(page);
            try (PDPageContentStream contentStream = new PDPageContentStream(document, page, AppendMode.APPEND, false))
            {
                contentStream.drawImage(ximage4, 0, 0);
            }
            
            document.save(TESTRESULTSDIR + "/singletiff.pdf");
        }
        
        try (PDDocument document = Loader.loadPDF(new File(TESTRESULTSDIR, "singletiff.pdf")))
        {
            assertEquals(2, document.getNumberOfPages());
        }  
    }
    
    /**
     * Tests CCITTFactory#createFromRandomAccess(PDDocument document,
     * RandomAccess reader) with a multi page TIFF
     */
    @Test
    void testCreateFromRandomAccessMulti() throws IOException
    {
        String tiffPath = "src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/ccittg4multi.tif";
        
        ImageInputStream is = ImageIO.createImageInputStream(new File(tiffPath));
        ImageReader imageReader = ImageIO.getImageReaders(is).next();
        imageReader.setInput(is);
        int countTiffImages = imageReader.getNumImages(true);
        assertTrue(countTiffImages > 1);
        
        try (PDDocument document = new PDDocument())
        {
            int pdfPageNum = 0;
            while (true)
            {
                PDImageXObject ximage = CCITTFactory.createFromFile(document, new File(tiffPath), pdfPageNum);
                if (ximage == null)
                {
                    break;
                }
                BufferedImage bim = imageReader.read(pdfPageNum);
                validate(ximage, 1, bim.getWidth(), bim.getHeight(), "tiff", PDDeviceGray.INSTANCE.getName());
                checkIdent(bim, ximage.getOpaqueImage(null, 1));
                PDPage page = new PDPage(PDRectangle.A4);
                float fX = ximage.getWidth() / page.getMediaBox().getWidth();
                float fY = ximage.getHeight() / page.getMediaBox().getHeight();
                float factor = Math.max(fX, fY);
                document.addPage(page);
                try (PDPageContentStream contentStream = new PDPageContentStream(document, page, AppendMode.APPEND, false))
                {
                    contentStream.drawImage(ximage, 0, 0, ximage.getWidth() / factor, ximage.getHeight() / factor);
                }
                ++pdfPageNum;
            }
            
            assertEquals(countTiffImages, pdfPageNum);
            
            document.save(TESTRESULTSDIR + "/multitiff.pdf");
        }
        
        try (PDDocument document = Loader.loadPDF(new File(TESTRESULTSDIR, "multitiff.pdf"), (String) null))
        {
            assertEquals(countTiffImages, document.getNumberOfPages());
        }  
        imageReader.dispose();
    }

    @Test
    void testCreateFromBufferedImage() throws IOException
    {
        String tiffG4Path = "src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/ccittg4.tif";

        try (PDDocument document = new PDDocument())
        {
            BufferedImage bim = ImageIO.read(new File(tiffG4Path));
            PDImageXObject ximage3 = CCITTFactory.createFromImage(document, bim);
            validate(ximage3, 1, 344, 287, "tiff", PDDeviceGray.INSTANCE.getName());
            checkIdent(bim, ximage3.getOpaqueImage(null, 1));

            PDPage page = new PDPage(PDRectangle.A4);
            document.addPage(page);
            try (PDPageContentStream contentStream = new PDPageContentStream(document, page, AppendMode.APPEND, false))
            {
                contentStream.drawImage(ximage3, 0, 0, ximage3.getWidth(), ximage3.getHeight());
            }

            document.save(TESTRESULTSDIR + "/singletifffrombi.pdf");
        }

        try (PDDocument document = Loader.loadPDF(new File(TESTRESULTSDIR, "singletifffrombi.pdf")))
        {
            assertEquals(1, document.getNumberOfPages());
        }  
    }    
    
    @Test
    void testCreateFromBufferedChessImage() throws IOException
    {
        try (PDDocument document = new PDDocument())
        {
            BufferedImage bim = new BufferedImage(343, 287, BufferedImage.TYPE_BYTE_BINARY);
            assertNotEquals((bim.getWidth() / 8) * 8, bim.getWidth()); // not mult of 8
            int col = 0;
            for (int x = 0; x < bim.getWidth(); ++x)
            {
                for (int y = 0; y < bim.getHeight(); ++y)
                {
                    bim.setRGB(x, y, col & 0xFFFFFF);
                    col = ~col;
                }
            }
            
            PDImageXObject ximage3 = CCITTFactory.createFromImage(document, bim);
            validate(ximage3, 1, 343, 287, "tiff", PDDeviceGray.INSTANCE.getName());
            checkIdent(bim, ximage3.getOpaqueImage(null, 1));
            
            PDPage page = new PDPage(PDRectangle.A4);
            document.addPage(page);
            try (PDPageContentStream contentStream = new PDPageContentStream(document, page, AppendMode.APPEND, false))
            {
                contentStream.drawImage(ximage3, 0, 0, ximage3.getWidth(), ximage3.getHeight());
            }
            
            document.save(TESTRESULTSDIR + "/singletifffromchessbi.pdf");
        }

        try (PDDocument document = Loader.loadPDF(new File(TESTRESULTSDIR, "singletifffromchessbi.pdf")))
        {
            assertEquals(1, document.getNumberOfPages());
        }
    }
    
    /**
     * Tests that CCITTFactory#createFromFile(PDDocument document, File file) doesn't lock the
     * source file
     */
    @Test
    void testCreateFromFileLock() throws IOException
    {
        // copy the source file to a temp directory, as we will be deleting it
        String tiffG3Path = "src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/ccittg3.tif";
        File copiedTiffFile = new File(TESTRESULTSDIR, "ccittg3.tif");
        Files.copy(new File(tiffG3Path).toPath(), copiedTiffFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
        PDDocument document = new PDDocument();
        CCITTFactory.createFromFile(document, copiedTiffFile);
        assertTrue(copiedTiffFile.delete());
    }

    /**
     * Tests that CCITTFactory#createFromFile(PDDocument document, File file, int number) doesn't
     * lock the source file
     */
    @Test
    void testCreateFromFileNumberLock() throws IOException
    {
        // copy the source file to a temp directory, as we will be deleting it
        String tiffG3Path = "src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/ccittg3.tif";
        File copiedTiffFile = new File(TESTRESULTSDIR, "ccittg3n.tif");
        Files.copy(new File(tiffG3Path).toPath(), copiedTiffFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
        PDDocument document = new PDDocument();
        CCITTFactory.createFromFile(document, copiedTiffFile, 0);
        assertTrue(copiedTiffFile.delete());
    }

    /**
     * Tests that byte/short tag values are read correctly (ignoring possible garbage in remaining
     * bytes).
     */
    @Test
    void testByteShortPaddedWithGarbage() throws IOException
    {
        try (PDDocument document = new PDDocument())
        {
            String basePath = "src/test/resources/org/apache/pdfbox/pdmodel/graphics/image/ccittg3-garbage-padded-fields";
            for (String ext : Arrays.asList(".tif", "-bigendian.tif"))
            {
                String tiffPath = basePath + ext;
                PDImageXObject ximage3 = CCITTFactory.createFromFile(document, new File(tiffPath));
                validate(ximage3, 1, 344, 287, "tiff", PDDeviceGray.INSTANCE.getName());
            }
        }
    }
}