ImageDecodingTest.java

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.tika.imageprocessing;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.ImageOutputStream;

import org.junit.jupiter.api.Test;

public class ImageDecodingTest {

    private static byte[] jpeg(int width, int height, boolean progressive) throws Exception {
        BufferedImage image = ImageFixtures.image(BufferedImage.TYPE_3BYTE_BGR, width, height,
                0xFFFFFFFF, 0xFF000000, 0.1);
        ImageWriter writer = ImageIO.getImageWritersByFormatName("jpeg").next();
        ImageWriteParam param = writer.getDefaultWriteParam();
        if (progressive) {
            param.setProgressiveMode(ImageWriteParam.MODE_DEFAULT);
        }
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        try (ImageOutputStream ios = ImageIO.createImageOutputStream(out)) {
            writer.setOutput(ios);
            writer.write(null, new IIOImage(image, null, null), param);
        } finally {
            writer.dispose();
        }
        return out.toByteArray();
    }

    /** The jpeg with its frame header claiming {@code width} x {@code height}. */
    private static byte[] claimSize(byte[] jpeg, int width, int height) {
        byte[] patched = jpeg.clone();
        for (int i = 2; i + 8 < patched.length; i++) {
            int marker = patched[i + 1] & 255;
            if ((patched[i] & 255) == 0xFF && (marker == 0xC0 || marker == 0xC2)) {
                patched[i + 5] = (byte) (height >> 8);
                patched[i + 6] = (byte) height;
                patched[i + 7] = (byte) (width >> 8);
                patched[i + 8] = (byte) width;
                return patched;
            }
        }
        throw new IllegalStateException("no frame header");
    }

    @Test
    public void testProgressiveJpegsAreRecognized() throws Exception {
        assertTrue(ImageDecoding.mayBeProgressiveJpeg(jpeg(64, 64, true)));
        assertFalse(ImageDecoding.mayBeProgressiveJpeg(jpeg(64, 64, false)));
        assertFalse(ImageDecoding.mayBeProgressiveJpeg(ImageFixtures.png(64, 64, 0.1)));
        // the frame header is past what was read: assume the worst
        assertTrue(ImageDecoding.mayBeProgressiveJpeg(new byte[] {(byte) 0xFF, (byte) 0xD8,
                (byte) 0xFF, (byte) 0xE1, 0x10, 0x00, 0, 0}));
    }

    @Test
    public void testALargeProgressiveJpegIsNotDecoded() throws Exception {
        byte[] large = claimSize(jpeg(64, 64, true), 5000, 5000);
        assertArrayEquals(new long[] {5000, 5000}, ImageDecoding.size(ImageFixtures.probe(large)));
        assertNull(ImageDecoding.gray(ImageFixtures.probe(large)));
        assertNotNull(ImageDecoding.gray(ImageFixtures.probe(jpeg(64, 64, true))));
    }
}