algorithm.ImageImageFrameExpanding.java Source code

Java tutorial

Introduction

Here is the source code for algorithm.ImageImageFrameExpanding.java

Source

/*
 * This project has received funding from the European Unions Seventh 
 * Framework Programme for research, technological development and 
 * demonstration under grant agreement no FP7-601138 PERICLES.
 * 
 * Copyright 2015 Anna Eggers, State- and Univeristy Library Goettingen
 * 
 * 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 algorithm;

import static main.Configuration.LIBRARY_DIRECTORY;
import static main.Configuration.OUTPUT_DIRECTORY;
import static main.Configuration.RESTORED_DIRECTORY;
import static model.Criterion.CARRIER_PROCESSABILITY;
import static model.Criterion.CARRIER_RESTORABILITY;
import static model.Criterion.COMPRESSION;
import static model.Criterion.DETECTABILITY;
import static model.Criterion.ENCAPSULATION_METHOD;
import static model.Criterion.ENCRYPTION;
import static model.Criterion.PAYLOAD_ACCESSIBILITY;
import static model.Criterion.PAYLOAD_RESTORABILITY;
import static model.Criterion.STANDARDS;
import static model.Criterion.VELOCITY;
import static model.Criterion.VISIBILITY;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import javax.imageio.ImageIO;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.SuffixFileFilter;

import model.PayloadSegment;
import model.RestoredFile;
import model.Scenario;

/**
 * This technique works similar to closing credits of a movie, but for images. It is developed by Anna Eggers.
 *
 * This algorithm works on PNG images. It extends the carrier image with
 * additional pixels, which are the pixels from the payload image. The carrier
 * image and payload file can be restored correctly, using this technique.The
 * algorithm is able to append more than one payload file. This algorithm can
 * only append image payload. For other payload files, have a look at the Image
 * Information Embedding Frame algorithm.
 *
 * @author Anna Eggers
 */
public class ImageImageFrameExpanding extends AbstractAlgorithm {

    protected final int METADATA_HEIGHT = 200;
    private final List<File> tmpFiles = new ArrayList<File>();

    /*
     * ******* ENCAPSULATION *************
     */

    /**
     * Attach all payload images to the carrier
     */
    @Override
    public File encapsulate(File carrier, List<File> payloadList) throws IOException {
        File outputFile = appendAllPayload(carrier, payloadList);
        clearTmpFiles();
        return outputFile;
    }

    /**
     * Attach all payload images to the carrier image.
     * 
     * @param carrier
     * @param payloadList
     * @return encapsulated file
     * @throws IOException
     */
    private File appendAllPayload(File carrier, List<File> payloadList) throws IOException {
        File outputFile = getOutputFile(carrier);
        FileUtils.copyFile(carrier, outputFile);
        for (File payload : payloadList) {
            append(outputFile, payload);
        }
        return outputFile;
    }

    /**
     * Merge of only two images. This is used by the
     * {@link ImageInformationEmbeddingFrame} algorithm.
     * 
     * @param carrier
     * @param payload
     * @return encapsulated images
     * @throws IOException
     */
    public File encapsulate(File carrier, File payload) throws IOException {
        File outputFile = getOutputFile(carrier);
        FileUtils.copyFile(carrier, outputFile);
        append(outputFile, payload);
        return outputFile;
    }

    /**
     * This method appends the payload image to the bottom of the carrier image.
     * Furthermore restoration information is added.
     * 
     * @param carrier
     *            The file where the payload should be added
     * @param payload
     * @throws IOException
     */
    private void append(File carrier, File payload) throws IOException {
        BufferedImage carrierImage = ImageIO.read(carrier);
        BufferedImage payloadImage = ImageIO.read(payload);
        if (carrierImage == null || payloadImage == null) {
            return;// "This wan't an image! Return."
        }
        BufferedImage outputImage = getOutputImage(carrierImage, payloadImage);
        BufferedImage metadataImage = getMetadataImage(carrier, payload);
        Graphics graphics = outputImage.getGraphics();
        graphics.drawImage(carrierImage, 0, 0, null);
        graphics.drawImage(payloadImage, 0, carrierImage.getHeight(), null);
        graphics.drawImage(metadataImage, 0, carrierImage.getHeight() + payloadImage.getHeight(), null);
        writeImage(carrier, outputImage);
    }

    private BufferedImage getOutputImage(BufferedImage carrierImage, BufferedImage payloadImage) {
        int outputWidth = Math.max(carrierImage.getWidth(), payloadImage.getWidth());
        int outputHeight = carrierImage.getHeight() + payloadImage.getHeight() + METADATA_HEIGHT;
        return new BufferedImage(outputWidth, outputHeight, carrierImage.getType());
    }

    /**
     * Append restoration metadata. Size of carrier and payload files is added.
     * 
     * @param carrier
     * @param payload
     * @return metadata image
     * @throws IOException
     */
    protected BufferedImage getMetadataImage(File carrier, File payload) throws IOException {
        BufferedImage carrierBuffered = ImageIO.read(carrier);
        BufferedImage payloadBuffered = ImageIO.read(payload);
        BufferedImage metadataImage = new BufferedImage(
                Math.max(carrierBuffered.getWidth(), payloadBuffered.getWidth()), METADATA_HEIGHT,
                carrierBuffered.getType());
        colorizeImage(metadataImage, Color.blue.getRGB());
        File metadataImageFile = new File(OUTPUT_DIRECTORY + "metadataImage.png");
        writeImage(metadataImageFile, metadataImage);
        File metadataFile = new File(OUTPUT_DIRECTORY + "tmpMetadataText.txt");
        tmpFiles.add(metadataFile);
        PayloadSegment payloadSegment = new PayloadSegment(carrier, payload, this);
        // add height and width of carrier and payload:
        payloadSegment.addOptionalProperty("carrierWidth", "" + carrierBuffered.getWidth());
        payloadSegment.addOptionalProperty("carrierHeight", "" + carrierBuffered.getHeight());
        payloadSegment.addOptionalProperty("payloadWidth", "" + payloadBuffered.getWidth());
        payloadSegment.addOptionalProperty("payloadHeight", "" + payloadBuffered.getHeight());
        byte[] metadata = payloadSegment.getRestorationMetadataBytes();
        FileUtils.writeByteArrayToFile(metadataFile, metadata);
        OpenStegoRandomLSBSteganography lsbAlgorithm = new OpenStegoRandomLSBSteganography();
        File tmpOutputFile = lsbAlgorithm.encapsulate(metadataImageFile, metadataFile);
        tmpFiles.add(tmpOutputFile);
        metadataImage = ImageIO.read(tmpOutputFile);
        return metadataImage;
    }

    /**
     * Colorises the image. (The metadata image is blue.)
     */
    private void colorizeImage(BufferedImage image, int color) {
        for (int x = 0; x < image.getWidth(); x++) {
            for (int y = 0; y < METADATA_HEIGHT; y++) {
                image.setRGB(x, y, color);
            }
        }
    }

    /*
     * ******* RESTORATION *************
     */

    /**
     * Cuts away the payload from the carrier to restore the carrier. Recovers
     * the payload file. This method calls a recurse method, to recover all
     * attached payload files.
     * 
     * The encapsulated data image consists of the carrier image, the payload
     * image, and the restoration metadata image.
     */
    @Override
    public List<RestoredFile> restore(File carrier) throws IOException {
        List<RestoredFile> restoredFiles = new ArrayList<RestoredFile>();
        Properties restorationMetadata = getRestorationMetadata(carrier);
        if (payloadAttached(restorationMetadata)) {
            restoredFiles.addAll(recursivelyRestoreAll(carrier, restorationMetadata));
            updateRelatedFilesMetadata(restoredFiles);
        }
        clearTmpFiles();
        return restoredFiles;
    }

    /**
     * Recursively restores all payload files, and adds the last restored
     * carrier to the restored files list.
     * 
     * @param encapsulatedImages
     * @param restorationMetadata
     * @return restored files
     * @throws IOException
     */
    private List<RestoredFile> recursivelyRestoreAll(File encapsulatedImages, Properties restorationMetadata)
            throws IOException {
        List<RestoredFile> restoredFiles = new ArrayList<RestoredFile>();
        restoredFiles.add(restorePayload(encapsulatedImages, restorationMetadata));
        RestoredFile carrier = restoreCarrier(encapsulatedImages, restorationMetadata);
        // There are more payload files, if there is further valid restoration
        // metadata attached -> one more iteration:
        Properties furtherMetadata = getRestorationMetadata(carrier);
        if (payloadAttached(furtherMetadata)) {
            restoredFiles.addAll(recursivelyRestoreAll(carrier, furtherMetadata));
        } else { // last carrier! -> break
            restoredFiles.add(carrier);
        }
        return restoredFiles;
    }

    /**
     * There is another payload file attached, if there is valid restoration
     * metadata.
     * 
     * @param restorationMetadata
     * @return true if another payload file is attached
     */
    private boolean payloadAttached(Properties restorationMetadata) {
        return restorationMetadata != null && restorationMetadata.size() > 0;
    }

    /**
     * This method saves the information that the encapsulated files are related
     * to each other
     * 
     * @param restoredFiles
     */
    private void updateRelatedFilesMetadata(List<RestoredFile> restoredFiles) {
        for (RestoredFile restoredFile1 : restoredFiles) {
            for (RestoredFile restoredFile2 : restoredFiles) {
                if (restoredFile1 != restoredFile2) {
                    restoredFile1.relatedFiles.add(restoredFile2);
                }
            }
        }
    }

    /**
     * This method returns the restoration metadata as {@link Properties} class.
     * The restoration metadata contains among others the height and width of
     * the original carrier and payload images, which is necessary to restore
     * them.
     * 
     * @param encapsulatedData
     * @return Should return null, if no valid restoration data is available!
     * @throws IOException
     */
    protected Properties getRestorationMetadata(File encapsulatedData) {
        try {
            File embeddedRestorationMetadata = restoreRestorationMetadataImage(encapsulatedData);
            OpenStegoRandomLSBSteganography lsbAlgorithm = new OpenStegoRandomLSBSteganography();
            List<RestoredFile> restoredFiles = lsbAlgorithm.restore(embeddedRestorationMetadata);
            tmpFiles.addAll(restoredFiles);
            RestoredFile restorationMetadata = getRestorationMetadataFile(restoredFiles);
            if (restorationMetadata == null) {
                return null;
            }
            InputStream inputStream = new FileInputStream(restorationMetadata);
            Properties metadata = new Properties();
            metadata.load(inputStream);
            inputStream.close();
            return metadata;
        } catch (Exception e) {
        }
        return null;
    }

    /**
     * Returns the image in which the restoration metadata is embedded. This
     * image is appended at the end of the encapsulated data image.
     * 
     * The restoration metadata image contains the restoration metadata file,
     * which was embedded with the lsb steganography algorithm and has to be
     * restored afterwards.
     * 
     * @param encapsulatedData
     * @return image with embedded restoration data
     * @throws IOException
     */
    protected File restoreRestorationMetadataImage(File encapsulatedData) {
        try {
            BufferedImage encapsulatedImage = ImageIO.read(encapsulatedData);
            if (encapsulatedImage.getHeight() < METADATA_HEIGHT) {
                return null;
            }
            BufferedImage restorationMetadataBuffered = encapsulatedImage.getSubimage(0,
                    encapsulatedImage.getHeight() - METADATA_HEIGHT, encapsulatedImage.getWidth(), METADATA_HEIGHT);
            File embeddedRestorationMetadata = new File("tmpRestorationMetadata.png");
            tmpFiles.add(embeddedRestorationMetadata);
            writeImage(embeddedRestorationMetadata, restorationMetadataBuffered);
            return embeddedRestorationMetadata;
        } catch (IOException e) {
            return null;
        }
    }

    /**
     * The payload file of the restored files is the restoration metadata file.
     * 
     * @param restoredFiles
     * @return the restoration metadata file. Should return null in case there
     *         is no restoration metadata file.
     */
    protected RestoredFile getRestorationMetadataFile(List<RestoredFile> restoredFiles) {
        if (restoredFiles.size() != 2) {
            return null;
        }
        RestoredFile restorationMetadata = null;
        if (restoredFiles.get(0).wasPayload) {
            restorationMetadata = restoredFiles.get(0);
        } else {
            restorationMetadata = restoredFiles.get(1);
        }
        return restorationMetadata;
    }

    /**
     * Get the carrier image from the encapsulated data.
     * 
     * @param encapsulatedImage
     * @param restorationMetadata
     * @return carrier image
     * @throws IOException
     */
    protected RestoredFile restoreCarrier(File encapsulatedImage, Properties restorationMetadata)
            throws IOException {
        BufferedImage encapsulatedImageBuffered = ImageIO.read(encapsulatedImage);
        int carrierWidth = Integer.parseInt(restorationMetadata.getProperty("carrierWidth"));
        int carrierHeight = Integer.parseInt(restorationMetadata.getProperty("carrierHeight"));
        BufferedImage carrierBuffered = encapsulatedImageBuffered.getSubimage(0, 0, carrierWidth, carrierHeight);
        String originalCarrierPath = restorationMetadata.getProperty("carrierPath");
        RestoredFile carrier = new RestoredFile(RESTORED_DIRECTORY + Paths.get(originalCarrierPath).getFileName());
        writeImage(carrier, carrierBuffered);
        carrier.wasCarrier = true;
        carrier.wasPayload = false;
        carrier.checksumValid = false;
        carrier.originalFilePath = originalCarrierPath;
        carrier.algorithm = this;
        carrier.restorationNote = "It is not possible to get exactly the same checksum, because of internal lossless PNG compression mechanisms.";
        return carrier;
    }

    /**
     * Get the payload image, in which the payload file is embedded, from the
     * encapsulated data.
     * 
     * @param encapsulatedImage
     * @param restorationMetadata
     * @return payload image
     * @throws IOException
     */
    protected RestoredFile restorePayload(File encapsulatedImage, Properties restorationMetadata)
            throws IOException {
        BufferedImage encapsulatedImageBuffered = ImageIO.read(encapsulatedImage);
        int payloadWidth = Integer.parseInt(restorationMetadata.getProperty("payloadWidth"));
        int payloadHeight = Integer.parseInt(restorationMetadata.getProperty("payloadHeight"));
        int carrierHeight = Integer.parseInt(restorationMetadata.getProperty("carrierHeight"));
        BufferedImage payloadBuffered = encapsulatedImageBuffered.getSubimage(0, carrierHeight, payloadWidth,
                payloadHeight);
        RestoredFile payload = new RestoredFile(
                RESTORED_DIRECTORY + Paths.get(restorationMetadata.getProperty("payloadPath")).getFileName());
        writeImage(payload, payloadBuffered);
        payload.wasPayload = true;
        payload.wasCarrier = false;
        // The restored file attributes are not important here,
        // as this is not the real payload (which is embedded in this payload
        // holding image)
        return payload;
    }

    /**
     * Write a buffered output image to the output file
     * 
     * @param output
     * @param outputImage
     * @throws IOException
     */
    private void writeImage(File output, BufferedImage outputImage) throws IOException {
        if (!output.exists()) {
            output.createNewFile();
        }
        ImageIO.write(outputImage, "png", output);
    }

    /**
     * Deletes all temporary files.
     */
    private void clearTmpFiles() {
        for (File file : tmpFiles) {
            file.delete();
        }
        tmpFiles.clear();
    }

    /*
     * ********** CONFIGURATION ************
     */

    /**
     * Name of this algorithm to be shown in the GUI list.
     */
    @Override
    public String getName() {
        return "Image-image frame expanding";
    }

    /**
     * Returns the description of this algorithm to be shown in the user
     * interfaces.
     */
    @Override
    public String getDescription() {
        String description = "This algorithm currently works only on PNG images. It extends the carrier image with additional pixels, which are the pixels"
                + "from the payload image. The carrier image and payload file can be restored correctly, "
                + "using this technique.The algorithm is able to append more than one payload file.\n"
                + "This algorithm can only append image payload. For other payload files, have a look at the Image Information Embedding Frame algorithm.";
        if (!new File(LIBRARY_DIRECTORY + "f5.jar").isFile()) {
            description = "WARNING: This algorithm depends on openstego, but openstego.jar doesn't exist.\n\n"
                    + description;
        }
        return description;
    }

    /**
     * This is a restorable embedding algorithm.
     */
    @Override
    Scenario defineScenario() {
        Scenario scenario = new Scenario("Image image frame ideal scenario");
        scenario.description = "This is the ideal scenario to use the image image frame algorithm.";
        scenario.setCriterionValue(ENCAPSULATION_METHOD, EMBEDDING);
        scenario.setCriterionValue(VISIBILITY, VISIBLE);
        scenario.setCriterionValue(DETECTABILITY, DETECTABLE);
        scenario.setCriterionValue(CARRIER_RESTORABILITY, YES);
        scenario.setCriterionValue(PAYLOAD_RESTORABILITY, YES);
        scenario.setCriterionValue(CARRIER_PROCESSABILITY, YES);
        scenario.setCriterionValue(PAYLOAD_ACCESSIBILITY, NO);
        scenario.setCriterionValue(ENCRYPTION, NO);
        scenario.setCriterionValue(COMPRESSION, NO);
        scenario.setCriterionValue(VELOCITY, NO);
        scenario.setCriterionValue(STANDARDS, NO);
        return scenario;
    }

    /**
     * All carrier and payload file have to be images.
     */
    @Override
    SuffixFileFilter configureCarrierFileFilter() {
        List<String> supportedFileFormats = new ArrayList<String>();
        supportedFileFormats.add("png");
        return new SuffixFileFilter(supportedFileFormats);
    }

    /**
     * All carrier and payload file have to be images.
     */
    @Override
    SuffixFileFilter configurePayloadFileFilter() {
        return configureCarrierFileFilter();
    }

    /**
     * This algorithm outputs only images.
     */
    @Override
    SuffixFileFilter configureDecapsulationFileFilter() {
        return configureCarrierFileFilter();
    }

    /**
     * All files have to be images, and there has to be a minimum of one carrier
     * and one payload file.
     */
    @Override
    public boolean fulfilledTechnicalCriteria(File carrier, List<File> payloadList) {
        return carrier.isFile() && payloadList.size() > 0 && new File(LIBRARY_DIRECTORY + "openstego.jar").isFile();
    }
}