<commit_before>/**
 * Copyright (c) 2014 by <NAME>.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Any modifications to this file must keep this entire header intact.
 *
 * Change History - most recent changes go on top of previous changes
 *
 * Exif.java
 *
 * Who   Date       Description
 * ====  =======    =================================================
 * WY    06May2014  Complete rewrite to support adding thumbnail IFD
 */

package cafe.image.meta.exif;

import java.awt.image.BufferedImage;
import java.io.OutputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

import cafe.image.ImageIO;
import cafe.image.core.ImageMeta;
import cafe.image.core.ImageType;
import cafe.image.jpeg.Marker;
import cafe.image.options.JPEGOptions;
import cafe.image.tiff.ASCIIField;
import cafe.image.tiff.IFD;
import cafe.image.tiff.LongField;
import cafe.image.tiff.RationalField;
import cafe.image.tiff.ShortField;
import cafe.image.tiff.Tag;
import cafe.image.tiff.TiffFieldEnum;
import cafe.image.tiff.TiffField;
import cafe.image.tiff.TiffTag;
import cafe.image.util.IMGUtils;
import cafe.image.writer.ImageWriter;
import cafe.io.IOUtils;
import cafe.io.MemoryCacheRandomAccessOutputStream;
import cafe.io.RandomAccessOutputStream;
import cafe.io.WriteStrategyMM;

/**
 * EXIF wrapper
 *  
 * @author <NAME>, <EMAIL>
 * @version 1.0 01/08/2014
 */
public class Exif {
	// EXIF thumbnail flavors
	public static final int EXIF_FLAVOR_JPG = 0xFFD8;//JPEG SOI
	public static final int EXIF_FLAVOR_TIFF = 0x4D4D;//TIFF MM
	
	private int flavor;	
	private IFD imageIFD;
	private IFD exifSubIFD;
	private IFD gpsSubIFD;
	private BufferedImage thumbNail;
	private int firstIFDOffset = 0x08;	
	
	private boolean isThumbnailRequired;
	
	public Exif(int flavor) {
		if(flavor != Exif.EXIF_FLAVOR_JPG && flavor != Exif.EXIF_FLAVOR_TIFF)
			throw new IllegalArgumentException("Unknown EXIF flavor: " + flavor);
		this.flavor = flavor;
		if(flavor == Exif.EXIF_FLAVOR_JPG)
			createImageIFD();
	}	
	
	private void createImageIFD() {
		// Create Image IFD (IFD0)
		imageIFD = new IFD();
		TiffField<?> tiffField = new ASCIIField(TiffTag.IMAGE_DESCRIPTION.getValue(), "Exif created by JPEGTweaker\0");
		imageIFD.addField(tiffField);
		tiffField = new ASCIIField(TiffTag.MAKE.getValue(), "Yu's\0");
		imageIFD.addField(tiffField);
		tiffField = new ASCIIField(TiffTag.MODEL.getValue(), "JPEGTweaker 1.0\0");
		imageIFD.addField(tiffField);
		tiffField = new ShortField(TiffTag.ORIENTATION.getValue(), new short[]{1});
		imageIFD.addField(tiffField);
		tiffField = new RationalField(TiffTag.X_RESOLUTION.getValue(), new int[] {300, 1});
		imageIFD.addField(tiffField);
		tiffField = new RationalField(TiffTag.Y_RESOLUTION.getValue(), new int[] {300, 1});
		imageIFD.addField(tiffField);
		tiffField = new ShortField(TiffTag.RESOLUTION_UNIT.getValue(), new short[]{2});
		imageIFD.addField(tiffField);
		String softWare = "JPEGTweaker 1.0\0";
		tiffField = new ASCIIField(TiffTag.SOFTWARE.getValue(), softWare);
		imageIFD.addField(tiffField);
		DateFormat formatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
		tiffField = new ASCIIField(TiffTag.DATETIME.getValue(), formatter.format(new Date()) + '\0');
		imageIFD.addField(tiffField);
		tiffField = new LongField(TiffTag.EXIF_SUB_IFD.getValue(), new int[]{0}); // Place holder
		imageIFD.addField(tiffField);
	}
	
	public boolean hasThumbnail() {
		return thumbNail != null;
	}
	
	public void addExif(IFD exifSubIFD) {
		if(imageIFD != null)
			imageIFD.addChild(TiffTag.EXIF_SUB_IFD, exifSubIFD);
		else
			this.exifSubIFD = exifSubIFD;
	}
	
	public void addGPS(IFD gpsSubIFD) {
		if(imageIFD != null)
			imageIFD.addChild(TiffTag.GPS_SUB_IFD, gpsSubIFD);
		else
			this.gpsSubIFD = gpsSubIFD;
	}
	
	/**
	 * @param thumbNail optional thumbnail image. If null, will be generated from the input image
	 * @param flavor JPEG or TIFF flavor of thumbnail
	 */
	public void addThumbnail(BufferedImage thumbNail) {		
		this.thumbNail = thumbNail;
		this.isThumbnailRequired = true;
	}
	
	public boolean isThumbnailRequired() {
		return isThumbnailRequired;
	}	
	
	public void setThumbnail(BufferedImage thumbNail) {
		this.thumbNail = thumbNail;
	}
	
	public IFD getIFD(Tag tag) {
		if(tag.getValue() == TiffTag.EXIF_SUB_IFD.getValue()) {
			return exifSubIFD;
		} else if(tag.getValue() == TiffTag.GPS_SUB_IFD.getValue()) {
			return gpsSubIFD;
		}
		return null;
	}
	
	/** 
	 * Write the EXIF data to the OutputStream
	 * 
	 * @param os OutputStream
	 * @throws Exception 
	 */
	public void write(OutputStream os) throws Exception {
		RandomAccessOutputStream randOS = null;
		// Write the EXIF data according to the flavor
		if(flavor == Exif.EXIF_FLAVOR_JPG) {
			// Writes APP1 marker
			IOUtils.writeShortMM(os, Marker.APP1.getValue());
			// Wraps output stream with a RandomAccessOutputStream
			randOS = new MemoryCacheRandomAccessOutputStream(os);
			// TIFF structure starts here
			short endian = IOUtils.BIG_ENDIAN;
			short tiffID = 0x2a; //'*'
			randOS.setWriteStrategy(WriteStrategyMM.getInstance());
			randOS.writeShort(endian);
			randOS.writeShort(tiffID);
			// First IDF relative to TIFF structure
			randOS.seek(0x04);
			randOS.writeInt(firstIFDOffset);
			// Writes IFDs
			randOS.seek(firstIFDOffset);<commit_msg>Change comment<commit_after>/**
 * Copyright (c) 2014 by <NAME>.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Any modifications to this file must keep this entire header intact.
 *
 * Change History - most recent changes go on top of previous changes
 *
 * Exif.java
 *
 * Who   Date       Description
 * ====  =======    =================================================
 * WY    06May2014  Complete rewrite to support adding thumbnail IFD
 */

package cafe.image.meta.exif;

import java.awt.image.BufferedImage;
import java.io.OutputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

import cafe.image.ImageIO;
import cafe.image.core.ImageMeta;
import cafe.image.core.ImageType;
import cafe.image.jpeg.Marker;
import cafe.image.options.JPEGOptions;
import cafe.image.tiff.ASCIIField;
import cafe.image.tiff.IFD;
import cafe.image.tiff.LongField;
import cafe.image.tiff.RationalField;
import cafe.image.tiff.ShortField;
import cafe.image.tiff.Tag;
import cafe.image.tiff.TiffFieldEnum;
import cafe.image.tiff.TiffField;
import cafe.image.tiff.TiffTag;
import cafe.image.util.IMGUtils;
import cafe.image.writer.ImageWriter;
import cafe.io.IOUtils;
import cafe.io.MemoryCacheRandomAccessOutputStream;
import cafe.io.RandomAccessOutputStream;
import cafe.io.WriteStrategyMM;

/**
 * EXIF wrapper
 *  
 * @author <NAME>, <EMAIL>
 * @version 1.0 01/08/2014
 */
public class Exif {
	// EXIF thumbnail flavors
	public static final int EXIF_FLAVOR_JPG = 0xFFD8;//JPEG SOI
	public static final int EXIF_FLAVOR_TIFF = 0x4D4D;//TIFF MM
	
	private int flavor;	
	private IFD imageIFD;
	private IFD exifSubIFD;
	private IFD gpsSubIFD;
	private BufferedImage thumbNail;
	private int firstIFDOffset = 0x08;	
	
	private boolean isThumbnailRequired;
	
	public Exif(int flavor) {
		if(flavor != Exif.EXIF_FLAVOR_JPG && flavor != Exif.EXIF_FLAVOR_TIFF)
			throw new IllegalArgumentException("Unknown EXIF flavor: " + flavor);
		this.flavor = flavor;
		if(flavor == Exif.EXIF_FLAVOR_JPG)
			createImageIFD();
	}	
	
	private void createImageIFD() {
		// Create Image IFD (IFD0)
		imageIFD = new IFD();
		TiffField<?> tiffField = new ASCIIField(TiffTag.IMAGE_DESCRIPTION.getValue(), "Exif created by JPEGTweaker\0");
		imageIFD.addField(tiffField);
		tiffField = new ASCIIField(TiffTag.MAKE.getValue(), "Yu's\0");
		imageIFD.addField(tiffField);
		tiffField = new ASCIIField(TiffTag.MODEL.getValue(), "JPEGTweaker 1.0\0");
		imageIFD.addField(tiffField);
		tiffField = new ShortField(TiffTag.ORIENTATION.getValue(), new short[]{1});
		imageIFD.addField(tiffField);
		tiffField = new RationalField(TiffTag.X_RESOLUTION.getValue(), new int[] {300, 1});
		imageIFD.addField(tiffField);
		tiffField = new RationalField(TiffTag.Y_RESOLUTION.getValue(), new int[] {300, 1});
		imageIFD.addField(tiffField);
		tiffField = new ShortField(TiffTag.RESOLUTION_UNIT.getValue(), new short[]{2});
		imageIFD.addField(tiffField);
		String softWare = "JPEGTweaker 1.0\0";
		tiffField = new ASCIIField(TiffTag.SOFTWARE.getValue(), softWare);
		imageIFD.addField(tiffField);
		DateFormat formatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
		tiffField = new ASCIIField(TiffTag.DATETIME.getValue(), formatter.format(new Date()) + '\0');
		imageIFD.addField(tiffField);
		tiffField = new LongField(TiffTag.EXIF_SUB_IFD.getValue(), new int[]{0}); // Place holder
		imageIFD.addField(tiffField);
	}
	
	public boolean hasThumbnail() {
		return thumbNail != null;
	}
	
	public void addExif(IFD exifSubIFD) {
		if(imageIFD != null)
			imageIFD.addChild(TiffTag.EXIF_SUB_IFD, exifSubIFD);
		else
			this.exifSubIFD = exifSubIFD;
	}
	
	public void addGPS(IFD gpsSubIFD) {
		if(imageIFD != null)
			imageIFD.addChild(TiffTag.GPS_SUB_IFD, gpsSubIFD);
		else
			this.gpsSubIFD = gpsSubIFD;
	}
	
	/**
	 * @param thumbNail optional thumbnail image. If null, will be generated from the input image
	 * @param flavor JPEG or TIFF flavor of thumbnail
	 */
	public void addThumbnail(BufferedImage thumbNail) {		
		this.thumbNail = thumbNail;
		this.isThumbnailRequired = true;
	}
	
	public boolean isThumbnailRequired() {
		return isThumbnailRequired;
	}	
	
	public void setThumbnail(BufferedImage thumbNail) {
		this.thumbNail = thumbNail;
	}
	
	public IFD getIFD(Tag tag) {
		if(tag.getValue() == TiffTag.EXIF_SUB_IFD.getValue()) {
			return exifSubIFD;
		} else if(tag.getValue() == TiffTag.GPS_SUB_IFD.getValue()) {
			return gpsSubIFD;
		}
		return null;
	}
	
	/** 
	 * Write the EXIF data to the OutputStream
	 * 
	 * @param os OutputStream
	 * @throws Exception 
	 */
	public void write(OutputStream os) throws Exception {
		RandomAccessOutputStream randOS = null;
		// Write the EXIF data according to the flavor
		if(flavor == Exif.EXIF_FLAVOR_JPG) {
			// Writes APP1 marker
			IOUtils.writeShortMM(os, Marker.APP1.getValue());
			// Wraps output stream with a RandomAccessOutputStream
			randOS = new MemoryCacheRandomAccessOutputStream(os);
			// TIFF structure starts here
			short endian = IOUtils.BIG_ENDIAN;
			short tiffID = 0x2a; //'*'
			randOS.setWriteStrategy(WriteStrategyMM.getInstance());
			randOS.writeShort(endian);
			randOS.writeShort(tiffID);
			// First IFD offset relative to TIFF structure
			randOS.seek(0x04);
			randOS.writeInt(firstIFDOffset);
			// Writes IFDs
			randOS.seek(firstIFDOffset);
{'fraction_non_alphanumeric': 0.12015924719507781, 'fraction_numerical': 0.010857763300760043, 'mean_word_length': 4.479920674268716, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '504435', 'n_tokens_mistral': 4229, 'n_tokens_neox': 3822, 'n_words': 863}