Added Base64Wrapper to support different Base64 Implementaions (like android). Also added some functionality for android compatibility

This commit is contained in:
vanitasvitae 2015-04-28 22:47:56 +02:00
parent d7ccf6883c
commit 7cf965b1f5
9 changed files with 480 additions and 309 deletions

Binary file not shown.

View file

@ -1,9 +1,9 @@
public class ArrayUtils public class ArrayUtils
{ {
/** /**
* Return true, if the array part is sub array of src from offset * Return true, if the array part is sub array of src from offset
*
* @param src array * @param src array
* @param part array * @param part array
* @param offset int * @param offset int
@ -21,6 +21,7 @@ public class ArrayUtils
/** /**
* converts the byte array b into a char array by casting all the bytes into chars * converts the byte array b into a char array by casting all the bytes into chars
*
* @param b byte array * @param b byte array
* @return char array * @return char array
*/ */
@ -34,6 +35,7 @@ public class ArrayUtils
/** /**
* converts the char array into a byte array by casting all the chars into bytes * converts the char array into a byte array by casting all the chars into bytes
*
* @param c char array * @param c char array
* @return byte array * @return byte array
*/ */
@ -47,11 +49,13 @@ public class ArrayUtils
/** /**
* concatenate two byte arrays * concatenate two byte arrays
*
* @param first first byte array * @param first first byte array
* @param second second byte array * @param second second byte array
* @return first + second * @return first + second
*/ */
public static byte[] concatenate (byte[] first, byte[] second) { public static byte[] concatenate(byte[] first, byte[] second)
{
if (first == null) return second; if (first == null) return second;
if (second == null) return first; if (second == null) return first;
int aLen = first.length; int aLen = first.length;
@ -64,10 +68,12 @@ public class ArrayUtils
/** /**
* convert an integer into a 32 bit byte array * convert an integer into a 32 bit byte array
*
* @param value integer * @param value integer
* @return byte array * @return byte array
*/ */
public static final byte[] intToByteArray(int value) { public static final byte[] intToByteArray(int value)
{
return new byte[]{ return new byte[]{
(byte) (value >>> 24), (byte) (value >>> 24),
(byte) (value >>> 16), (byte) (value >>> 16),
@ -77,6 +83,7 @@ public class ArrayUtils
/** /**
* convert a signed byte array into an unsigned byte array (sort of) * convert a signed byte array into an unsigned byte array (sort of)
*
* @param b byte array of signed bytes * @param b byte array of signed bytes
* @return byte array of unsigned bytes * @return byte array of unsigned bytes
*/ */

9
src/Base64Wrapper.java Normal file
View file

@ -0,0 +1,9 @@
/**
* Created by vanitas on 28.04.15.
*/
public abstract class Base64Wrapper
{
public abstract byte[] decode(byte[] data);
public abstract byte[] encode(byte[] data);
}

View file

@ -1,5 +1,4 @@
public class Const public class Const
{ {
/** /**
@ -7,7 +6,9 @@ public class Const
*/ */
public static final int CHUNKSIZE = 65400; public static final int CHUNKSIZE = 65400;
/** Strings for JPEGUtils.decorate() */ /**
* Strings for JPEGUtils.decorate()
*/
public static final String EXIF = "EXIF"; public static final String EXIF = "EXIF";
public static final String STANDARDXMP = "StandardXMP"; public static final String STANDARDXMP = "StandardXMP";
@ -18,6 +19,11 @@ public class Const
*/ */
public static byte[] markJPG = {(byte) 0xFF, (byte) 0xD8}; public static byte[] markJPG = {(byte) 0xFF, (byte) 0xD8};
/**
* Magical bytes that idetify the file as PNG. (0x89 P N G) (length: 4)
*/
public static byte[] markPNG = {(byte) 0x89, (byte) 0x50, (byte) 0x4E, (byte) 0x47};
/** /**
* Marker that defines the beginning of a new block of metadata (FF E1) (length: 2) * Marker that defines the beginning of a new block of metadata (FF E1) (length: 2)
*/ */

View file

@ -1,23 +1,39 @@
import java.util.Base64;
public class DepthMapNeedle public class DepthMapNeedle
{ {
/** /**
* Interprete the arguments and execute the programm * Interprete the arguments and execute the programm
*
* @param args * @param args
*/ */
public static void main(String[] args) public static void main(String[] args)
{ {
Base64Wrapper wrapper = new Base64Wrapper(){
@Override
public byte[] decode(byte[] data)
{
return Base64.getDecoder().decode(data);
}
@Override
public byte[] encode(byte[] data)
{
return Base64.getEncoder().encode(data);
}
};
//No arguments given or '-h' as first argument -> show help text //No arguments given or '-h' as first argument -> show help text
if (args.length == 0 || args[0].equals("-h")) help(); if (args.length == 0 || args[0].equals("-h")) help();
//export depthmap //export depthmap
if(args.length >= 2 && args[0].equals("-e")) if (args.length >= 2 && args[0].equals("-d"))
{ {
for (int i = 1; i < args.length; i++) for (int i = 1; i < args.length; i++)
{ {
JPEG image = new JPEG(args[i]); JPEG image = new JPEG(args[i], wrapper);
if(image.exportDepthMap()) System.out.println("Depthmap extracted for file " + args[i]+"."); if (image.exportDepthMap())
System.out.println("Depthmap extracted for file "+args[i]+".");
else System.err.println("There is no Depthmap in file "+args[i]); else System.err.println("There is no Depthmap in file "+args[i]);
} }
} }
@ -27,21 +43,39 @@ public class DepthMapNeedle
{ {
for (int i = 1; i < args.length; i++) for (int i = 1; i < args.length; i++)
{ {
JPEG image = new JPEG(args[i]); JPEG image = new JPEG(args[i], wrapper);
if(image.exportSourceImage()) System.out.println("Unblurred source image extracted for file "+args[i]+"."); if (image.exportSourceImage())
else System.err.println("There is no unblurred source image in file "+args[i]+". Maybe this photo has not been taken with the blur function?"); System.out.println("Unblurred source image extracted for file "+args[i]+".");
else
System.err.println("There is no unblurred source image in file "+args[i]+". Maybe this photo has not been taken with the blur function?");
} }
} }
//inject depthmap //inject depthmap
else if(args.length >= 3 && args[0].equals("-i")) else if (args.length >= 3 && args[0].equals("-D"))
{ {
String depthmap = args[1]; String depthmap = args[1];
for (int i = 2; i < args.length; i++) for (int i = 2; i < args.length; i++)
{ {
JPEG image = new JPEG(args[i]); JPEG image = new JPEG(args[i], wrapper);
if(image.injectDepthMap(depthmap)) System.out.println("Depthmap injected into file "+args[i]+"."); if (image.injectDepthMap(depthmap))
else System.err.println("Something went wrong while injecting "+depthmap+" into "+args[i]+".\nRemember: The first argument has to be a png and the following arguments must be jpgs shot with the blur function."); System.out.println("Depthmap injected into file "+args[i]+".");
else
System.err.println("Something went wrong while injecting "+depthmap+" into "+args[i]+".\nRemember: The first argument has to be a png and the following arguments must be jpgs shot with the blur function.");
image.save();
}
}
//inject source image
else if (args.length >= 3 && args[0].equals("-S"))
{
String source = args[1];
for (int i = 2; i < args.length; i++)
{
JPEG image = new JPEG(args[i], wrapper);
if (image.injectSourceImage(source))
System.out.println("Source image injected into file "+args[i]+".");
else
System.err.println("Something went wrong while injecting "+source+" into "+args[i]+".\nRemember: The first argument has to be a jpg and the following arguments must be jpgs shot with the blur function.");
image.save(); image.save();
} }
} }
@ -56,12 +90,15 @@ public class DepthMapNeedle
+"\nDepthMapNeedle is a tool to inject or extract depth information in form of depthmaps from photos shot using Google Cameras Blur function." +"\nDepthMapNeedle is a tool to inject or extract depth information in form of depthmaps from photos shot using Google Cameras Blur function."
+"\n" +"\n"
+"\nAvailable Options:" +"\nAvailable Options:"
+ "\n'-e <file1>.jpg ... <fileN>.jpg':" +"\n'-d <file1>.jpg ... <fileN>.jpg':"
+"\n Extract the depthmap from the specified photo(s). The depthmaps will be stored with the suffix \"_d.png\"." +"\n Extract the depthmap from the specified photo(s). The depthmaps will be stored with the suffix \"_d.png\"."
+"\n'-s <file1>.jpg ... <fileN>.jpg':" +"\n'-s <file1>.jpg ... <fileN>.jpg':"
+"\n Extract the unblurred source image from the photo(s). These files will be stored with the suffix \"_s.jpg\"." +"\n Extract the unblurred source image from the photo(s). These files will be stored with the suffix \"_s.jpg\"."
+ "\n'-i <depthmap>.png <file1>.jpg <fileN>.jpg':" +"\n'-D <depthmap>.png <file1>.jpg ... <fileN>.jpg':"
+"\n Inject a png file as Depthmap into the following specified jpg files." +"\n Inject a png file as Depthmap into the following specified jpg files."
+"\n'-S <unblurred>.jpg <file1>.jpg ... <fileN>.jpg':"
+"\n Inject an unblurred source image into the following specified jpg files. "
+"\n Note: The unblurred source image is a simple jpg file of the same dimensions as the photos."
+"\n'-h':" +"\n'-h':"
+"\n Show this help text."); +"\n Show this help text.");
} }

View file

@ -1,5 +1,4 @@
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
@ -7,6 +6,7 @@ public class HexUtil
{ {
/** /**
* Print out a byte array in hexadecimal * Print out a byte array in hexadecimal
*
* @param bytes array * @param bytes array
*/ */
public static void printHex(byte[] bytes) public static void printHex(byte[] bytes)
@ -16,10 +16,12 @@ public class HexUtil
/** /**
* convert a byte to a hex string * convert a byte to a hex string
*
* @param data array * @param data array
* @return String representation in hex * @return String representation in hex
*/ */
public static String byteToHexString(byte data) { public static String byteToHexString(byte data)
{
StringBuffer buf = new StringBuffer(); StringBuffer buf = new StringBuffer();
buf.append(toHexChar((data >>> 4) & 0x0F)); buf.append(toHexChar((data >>> 4) & 0x0F));
@ -30,19 +32,24 @@ public class HexUtil
/** /**
* convert a integer into a hexadecimal character * convert a integer into a hexadecimal character
*
* @param i integer * @param i integer
* @return hex char * @return hex char
*/ */
public static char toHexChar(int i) { public static char toHexChar(int i)
if ((0 <= i) && (i <= 9)) { {
if ((0 <= i) && (i <= 9))
{
return (char) ('0'+i); return (char) ('0'+i);
} else { } else
{
return (char) ('a'+(i-10)); return (char) ('a'+(i-10));
} }
} }
/** /**
* Generate the md5 digest of the byte array data in hexadecimal * Generate the md5 digest of the byte array data in hexadecimal
*
* @param data array * @param data array
* @return byte array of the md5 digest in hex * @return byte array of the md5 digest in hex
*/ */

View file

@ -1,5 +1,4 @@
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
@ -10,10 +9,12 @@ public class IO
{ {
/** /**
* Read file from disk to byte array * Read file from disk to byte array
*
* @param file path to file * @param file path to file
* @return byte array * @return byte array
*/ */
public static byte[] read(File file) { public static byte[] read(File file)
{
try try
{ {
InputStream is = new FileInputStream(file); InputStream is = new FileInputStream(file);
@ -21,8 +22,7 @@ public class IO
is.read(b); is.read(b);
is.close(); is.close();
return b; return b;
} } catch (IOException e)
catch (IOException e)
{ {
System.err.println("Couldn't read file "+file.getAbsolutePath()); System.err.println("Couldn't read file "+file.getAbsolutePath());
e.printStackTrace(); e.printStackTrace();
@ -32,6 +32,7 @@ public class IO
/** /**
* Write byte array to file on disk * Write byte array to file on disk
*
* @param b byte array * @param b byte array
* @param filepath path to outputfile * @param filepath path to outputfile
*/ */

View file

@ -1,6 +1,5 @@
import java.io.File; import java.io.File;
import java.util.Base64;
public class JPEG extends Const public class JPEG extends Const
{ {
@ -10,50 +9,56 @@ public class JPEG extends Const
/** /**
* location of the source file * location of the source file
*/ */
private String filename; private String m_Filename;
/** /**
* byte array containing the actual file * byte array containing the actual file
*/ */
private byte[] rawData; private byte[] m_RawData;
/** /**
* byte array containing EXIF block information without header * byte array containing EXIF block information without header
*/ */
private byte[] exif; private byte[] m_Exif;
/** /**
* byte array containing StandardXMP block information without header * byte array containing StandardXMP block information without header
*/ */
private byte[] xmpSta; private byte[] m_StandardXMP;
/** /**
* byte array containing ExtendedXMP block information without header * byte array containing ExtendedXMP block information without header
*/ */
private byte[] xmpExt; private byte[] m_ExtendedXMP;
/** /**
* byte array conatining the trailing headless data block conatining the blurred jpeg image * byte array containing the trailing headless data block containing the blurred jpeg image
*/ */
private byte[] tail; private byte[] m_ImageTail;
private Base64Wrapper base64;
/** METHODS */ /** METHODS */
/** /**
* Constructor * Constructor
* @param rawData *
* @param rawData raw byte array
*/ */
public JPEG(byte[] rawData) public JPEG(byte[] rawData, Base64Wrapper wrapper)
{ {
this.base64 = wrapper;
this.init(rawData, null); this.init(rawData, null);
} }
/** /**
* Constructor, that reads a byte array from file * Constructor, that reads a byte array from file
* @param filename *
* @param filename raw byte array
*/ */
public JPEG(String filename) public JPEG(String filename, Base64Wrapper wrapper)
{ {
this.base64 = wrapper;
this.init(IO.read(new File(filename)), filename); this.init(IO.read(new File(filename)), filename);
} }
@ -62,246 +67,311 @@ public class JPEG extends Const
*/ */
public void disassemble() public void disassemble()
{ {
exif = this.getEXIFBlock(); m_Exif = this.getEXIFBlock();
xmpSta = this.getStandardXMPBlockContent(); m_StandardXMP = this.getStandardXMPBlockContent();
xmpExt = this.getExtendedXMPContent(); m_ExtendedXMP = this.getExtendedXMPContent();
tail = this.getImageTail(); m_ImageTail = this.getImageTail();
} }
/** /**
* extract the depth map from the jpeg and store it in the same location as the jpeg itself but with the suffix _d.png * extract the depth map from the jpeg and store it in the same location as the jpeg itself but with the suffix _d.png
*
* @return * @return
*/ */
public boolean exportDepthMap() public boolean exportDepthMap()
{ {
String out = filename; String out = m_Filename;
if (out.endsWith(".jpg") || out.endsWith(".JPG")) out = out.substring(0, out.length()-4); if (out.endsWith(".jpg") || out.endsWith(".JPG")) out = out.substring(0, out.length()-4);
return this.exportDepthMap(out+"_d.png"); return this.exportDepthMap(out+"_d.png");
} }
/** /**
* extract the depthmap from the jpeg and store it under the location specified in "file" * extract the depthmap from the jpeg and store it under the location specified in "file"
*
* @param file String of the output location * @param file String of the output location
* @return success * @return success
*/ */
public boolean exportDepthMap(String file) public boolean exportDepthMap(String file)
{ {
byte[] b64 = ArrayUtils.unsign(extractDepthMap()); byte[] b64 = ArrayUtils.unsign(extractDepthMap());
byte[] depth = Base64.getDecoder().decode(b64); byte[] depth = base64.decode(b64);
if (depth != null) if (depth != null)
{ {
IO.write(depth, file); IO.write(depth, file);
return true; return true;
} } else return false;
else return false;
} }
/** /**
* Extract and save the unblurred source image. The image will be saved in the same location as the jpeg itself, but with the suffix "_s.jpg" * Extract and save the unblurred source image. The image will be saved in the same location as the jpeg itself, but with the suffix "_s.jpg"
*
* @return success * @return success
*/ */
public boolean exportSourceImage() public boolean exportSourceImage()
{ {
String out = filename; String out = m_Filename;
if (out.endsWith(".jpg") || out.endsWith(".JPG")) out = out.substring(0, out.length()-4); if (out.endsWith(".jpg") || out.endsWith(".JPG")) out = out.substring(0, out.length()-4);
return this.exportSourceImage(out+"_s.jpg"); return this.exportSourceImage(out+"_s.jpg");
} }
/** /**
* Extract the unblurred source image and save it under the location specified in "file" * Extract the unblurred source image and save it under the location specified in "file"
*
* @param file String of the location * @param file String of the location
* @return success * @return success
*/ */
public boolean exportSourceImage(String file) public boolean exportSourceImage(String file)
{ {
byte[] b64 = ArrayUtils.unsign(extractSourceImage()); byte[] b64 = ArrayUtils.unsign(extractSourceImage());
byte[] src = Base64.getDecoder().decode(b64); byte[] src = base64.decode(b64);
if (src != null) if (src != null)
{ {
IO.write(src, file); IO.write(src, file);
return true; return true;
} } else return false;
else return false;
} }
/** /**
* Extract and return the depthmap information * Extract and return the depthmap information
*
* @return depthmap as byte array * @return depthmap as byte array
*/ */
public byte[] extractDepthMap() public byte[] extractDepthMap()
{ {
return JPEGUtils.extractDepthMap(rawData); return JPEGUtils.extractDepthMap(m_RawData);
} }
/** /**
* Extract and return the unblurred source image * Extract and return the unblurred source image
*
* @return source image as byte array * @return source image as byte array
*/ */
public byte[] extractSourceImage() public byte[] extractSourceImage()
{ {
return JPEGUtils.extractSourceImage(rawData); return JPEGUtils.extractSourceImage(m_RawData);
} }
/** /**
* Find all indizes of APP1 markers in the jpegs byte array * Find all indizes of APP1 markers in the jpegs byte array
*
* @return integer array of all positions of the APP1 marker in the rawData array * @return integer array of all positions of the APP1 marker in the rawData array
*/ */
public int[] getBoundaries() public int[] getBoundaries()
{ {
return JPEGUtils.getBoundaries(rawData); return JPEGUtils.getBoundaries(m_RawData);
} }
/** /**
* return the exif block of the jpeg as a byte array * return the exif block of the jpeg as a byte array
*
* @return exifblock (member) * @return exifblock (member)
*/ */
public byte[] getExif() public byte[] getExif()
{ {
return exif; return m_Exif;
} }
/** /**
* Gets the exif block from the rawData array instead from the member * Gets the exif block from the rawData array instead from the member
*
* @return the exif block from rawData * @return the exif block from rawData
*/ */
public byte[] getEXIFBlock() public byte[] getEXIFBlock()
{ {
return JPEGUtils.getEXIFBlock(rawData); return JPEGUtils.getEXIFBlock(m_RawData);
} }
/** /**
* Extract and return the content of all the ExtendedXMP blocks concatenated. * Extract and return the content of all the ExtendedXMP blocks concatenated.
*
* @return content of the ExtendedXMP blocks * @return content of the ExtendedXMP blocks
*/ */
public byte[] getExtendedXMPContent() public byte[] getExtendedXMPContent()
{ {
return JPEGUtils.getExtendedXMPBlockContent(rawData); return JPEGUtils.getExtendedXMPBlockContent(m_RawData);
} }
/** /**
* return the filename and path * return the filename and path
* @return *
* @return member filename
*/ */
public String getFilename() public String getFilename()
{ {
return filename; return m_Filename;
} }
/** /**
* Return the headless block of data in the image (this contains the JPEG you see when opening the file) * Return the headless block of data in the image (this contains the JPEG you see when opening the file)
*
* @return headless JPEG tail * @return headless JPEG tail
*/ */
public byte[] getImageTail() public byte[] getImageTail()
{ {
return JPEGUtils.getImageTail(rawData); return JPEGUtils.getImageTail(m_RawData);
} }
/** /**
* Return the Metadata of the image (content of all APP1 marked blocks) * Return the Metadata of the image (content of all APP1 marked blocks)
*
* @return metadata * @return metadata
*/ */
public byte[] getMetadata() public byte[] getMetadata()
{ {
return JPEGUtils.getMetadata(rawData); return JPEGUtils.getMetadata(m_RawData);
} }
/** /**
* Returns the byte array that IO.read(image) returned * Returns the byte array that IO.read(image) returned
*
* @return byte array of the image file * @return byte array of the image file
*/ */
public byte[] getRawData() public byte[] getRawData()
{ {
return this.rawData; return this.m_RawData;
} }
/** /**
* Extracts and returns the content of the StandardXMP block * Extracts and returns the content of the StandardXMP block
*
* @return content of the StandardXMP block * @return content of the StandardXMP block
*/ */
public byte[] getStandardXMPBlockContent() public byte[] getStandardXMPBlockContent()
{ {
return JPEGUtils.getStandardXMPBlockContent(rawData); return JPEGUtils.getStandardXMPBlockContent(m_RawData);
} }
/** /**
* Returns the Image tail (member) * Returns the Image tail (member)
*
* @return member image tail * @return member image tail
*/ */
public byte[] getTail() public byte[] getTail()
{ {
return tail; return m_ImageTail;
} }
/** /**
* Extracts and returns the content of all XMP blocks concatenated (StandardXMP + ExtendedXMPs) * Extracts and returns the content of all XMP blocks concatenated (StandardXMP + ExtendedXMPs)
*
* @return content of the XMP blocks * @return content of the XMP blocks
*/ */
public byte[] getXMPBlocksContent() public byte[] getXMPBlocksContent()
{ {
return JPEGUtils.getXMPBlocksContent(rawData); return JPEGUtils.getXMPBlocksContent(m_RawData);
} }
/** /**
* Return the member containing the ExtendedXMP information * Return the member containing the ExtendedXMP information
*
* @return XmpExt (member) * @return XmpExt (member)
*/ */
public byte[] getXmpExt() public byte[] getXmpExt()
{ {
return xmpExt; return m_ExtendedXMP;
} }
/** /**
* Return the member containing the StandardXMP information * Return the member containing the StandardXMP information
*
* @return xmpSta (member) * @return xmpSta (member)
*/ */
public byte[] getXmpSta() public byte[] getXmpSta()
{ {
return xmpSta; return m_StandardXMP;
} }
/** /**
* initializes the JPEG object. * initializes the JPEG object.
* set the array containing the image as member rawData, set filename * set the array containing the image as member rawData, set filename
* disassemble the Image and set the other members with content. * disassemble the Image and set the other members with content.
*
* @param raw byte array of the image * @param raw byte array of the image
* @param filename location * @param filename location
*/ */
private void init(byte[] raw, String filename) private void init(byte[] raw, String filename)
{ {
this.rawData = raw; if(ArrayUtils.arrayIsPartOfOtherArrayOnOffset(raw, Const.markJPG,0))
this.filename = filename; {
this.m_RawData = raw;
this.m_Filename = filename;
this.disassemble(); this.disassemble();
} }
}
/** /**
* Inject a new depthmap into the jpeg ("replace" the old GDepth:Data value with the Base64 encoded png file) * Inject a new depthmap into the jpeg ("replace" the old GDepth:Data value with the Base64 encoded png file)
*
* @param filename location of the new depthmap png * @param filename location of the new depthmap png
* @return success * @return success
*/ */
public boolean injectDepthMap(String filename) public boolean injectDepthMap(String filename)
{ {
byte[] depth = Base64.getEncoder().encode(IO.read(new File(filename))); byte[] depth = base64.encode(IO.read(new File(filename)));
xmpExt = JPEGUtils.replace(xmpExt, keyGDepthData, depth); byte[] newExtendedXMP = JPEGUtils.replace(m_ExtendedXMP, keyGDepthData, depth);
if(xmpExt != null) return true; if (newExtendedXMP != null)
else return false; {
m_ExtendedXMP = newExtendedXMP;
return true;
} else return false;
}
public boolean injectDepthMap(byte[] data)
{
byte[] depth = base64.encode(data);
byte[] newExtendedXMP = JPEGUtils.replace(m_ExtendedXMP, keyGDepthData, depth);
if (newExtendedXMP != null)
{
m_ExtendedXMP = newExtendedXMP;
return true;
} else return false;
}
/**
* Inject a new unblurred source image into the jpeg ("replace" the old GImage:Data value with the Base64 encoded jpg file)
*
* @param filename location of the new unblurred source image jpg
* @return success
*/
public boolean injectSourceImage(String filename)
{
byte[] image = base64.encode(IO.read(new File(filename)));
byte[] newExtendedXMP = JPEGUtils.replace(m_ExtendedXMP, keyGImageData, image);
if (newExtendedXMP != null)
{
m_ExtendedXMP = newExtendedXMP;
return true;
} else return false;
}
public boolean injectSourceImage(byte[] data)
{
byte[] image = base64.encode(data);
byte[] newExtendedXMP = JPEGUtils.replace(m_ExtendedXMP, keyGImageData, image);
if (newExtendedXMP != null)
{
m_ExtendedXMP = newExtendedXMP;
return true;
} else return false;
} }
/** /**
* Reassemble a functional jpeg byte array from block data. * Reassemble a functional jpeg byte array from block data.
* Set rawData to the new array and also return it * Set rawData to the new array and also return it
*
* @return reassembled jpeg byte array * @return reassembled jpeg byte array
*/ */
public byte[] reassemble() public byte[] reassemble()
{ {
byte[] md5 = HexUtil.generateMD5(xmpExt); byte[] md5 = HexUtil.generateMD5(m_ExtendedXMP);
byte[] out = markJPG; byte[] out = markJPG;
out = ArrayUtils.concatenate(out, JPEGUtils.decorateBlock(exif,EXIF)); out = ArrayUtils.concatenate(out, JPEGUtils.decorateBlock(m_Exif, EXIF));
out = ArrayUtils.concatenate(out, JPEGUtils.decorateBlock(xmpSta, STANDARDXMP)); out = ArrayUtils.concatenate(out, JPEGUtils.decorateBlock(m_StandardXMP, STANDARDXMP));
out = ArrayUtils.concatenate(out, JPEGUtils.decorateBlock(xmpExt, EXTENDEDXMP)); out = ArrayUtils.concatenate(out, JPEGUtils.decorateBlock(m_ExtendedXMP, EXTENDEDXMP));
out = ArrayUtils.concatenate(out, tail); out = ArrayUtils.concatenate(out, m_ImageTail);
out = JPEGUtils.replace(out, keyHasExtendedXMP, md5); out = JPEGUtils.replace(out, keyHasExtendedXMP, md5);
this.rawData = out; this.m_RawData = out;
return out; return out;
} }
@ -310,11 +380,12 @@ public class JPEG extends Const
*/ */
public void save() public void save()
{ {
this.save(filename); this.save(m_Filename);
} }
/** /**
* Write the image to the file specified in "file" * Write the image to the file specified in "file"
*
* @param file * @param file
*/ */
public void save(String file) public void save(String file)
@ -324,37 +395,41 @@ public class JPEG extends Const
/** /**
* Set the exif member to a new byte array * Set the exif member to a new byte array
*
* @param exif new array * @param exif new array
*/ */
public void setExif(byte[] exif) public void setExif(byte[] exif)
{ {
this.exif=exif; this.m_Exif = exif;
} }
/** /**
* Set the tail member array to a new byte array * Set the tail member array to a new byte array
*
* @param tail new array * @param tail new array
*/ */
public void setTail(byte[] tail) public void setTail(byte[] tail)
{ {
this.tail=tail; this.m_ImageTail = tail;
} }
/** /**
* set the xmpExt member array to a new byte array * set the xmpExt member array to a new byte array
*
* @param xmpExt new array * @param xmpExt new array
*/ */
public void setXmpExt(byte[] xmpExt) public void setXmpExt(byte[] xmpExt)
{ {
this.xmpExt=xmpExt; this.m_ExtendedXMP = xmpExt;
} }
/** /**
* set the xmpSta member array to a new byte array * set the xmpSta member array to a new byte array
*
* @param xmpSta new array * @param xmpSta new array
*/ */
public void setXmpSta(byte[] xmpSta) public void setXmpSta(byte[] xmpSta)
{ {
this.xmpSta=xmpSta; this.m_StandardXMP = xmpSta;
} }
} }

View file

@ -1,5 +1,4 @@
import java.util.Arrays; import java.util.Arrays;
import java.util.Vector; import java.util.Vector;
@ -7,6 +6,7 @@ public class JPEGUtils extends Const
{ {
/** /**
* Read the length of the block from the two bytes after the APP1 Marker * Read the length of the block from the two bytes after the APP1 Marker
*
* @param boundaryPos position of the APP1 Marker in the byte array * @param boundaryPos position of the APP1 Marker in the byte array
* @param data byte array containing the block WITH APP1 Marker * @param data byte array containing the block WITH APP1 Marker
* @return length of the block * @return length of the block
@ -24,8 +24,7 @@ public class JPEGUtils extends Const
try try
{ {
o = 256 * (data[boundaryPos+2] & 0xFF)+(data[boundaryPos+3] & 0xFF); o = 256 * (data[boundaryPos+2] & 0xFF)+(data[boundaryPos+3] & 0xFF);
} } catch (ArrayIndexOutOfBoundsException e)
catch(ArrayIndexOutOfBoundsException e)
{ {
System.err.println("JPEGUtils blockLength threw ArrayIndexOutOfBoundsException. Maybe the block is cut after APP1 Marker?"); System.err.println("JPEGUtils blockLength threw ArrayIndexOutOfBoundsException. Maybe the block is cut after APP1 Marker?");
e.printStackTrace(); e.printStackTrace();
@ -39,6 +38,7 @@ public class JPEGUtils extends Const
* In case, type == EXTENDEDXMP and data.length > CHUNKSIZE multiple ExtendedXMPBlocks will be generated and concatenated. * In case, type == EXTENDEDXMP and data.length > CHUNKSIZE multiple ExtendedXMPBlocks will be generated and concatenated.
* Available types: * Available types:
* Const.EXIF, Const.STANDARDXMP, Const.EXTENDEDXMP * Const.EXIF, Const.STANDARDXMP, Const.EXTENDEDXMP
*
* @param data byte array of data that will be decorated * @param data byte array of data that will be decorated
* @param type String declaring the type of header for the block. * @param type String declaring the type of header for the block.
* @return decorated block * @return decorated block
@ -101,6 +101,7 @@ public class JPEGUtils extends Const
* Extract the value of a key from the data Array. * Extract the value of a key from the data Array.
* For Example: data = 'blabalkey="Hallo"blabla', key = 'key' * For Example: data = 'blabalkey="Hallo"blabla', key = 'key'
* Output of extract(data, key) will be 'Hallo' * Output of extract(data, key) will be 'Hallo'
*
* @param data array of bytes * @param data array of bytes
* @param key array that contains the key * @param key array that contains the key
* @return the "value" of the key (the part after the key and the following '="' to the next '"'. * @return the "value" of the key (the part after the key and the following '="' to the next '"'.
@ -132,6 +133,7 @@ public class JPEGUtils extends Const
/** /**
* Extract the depth information from a byte array (image as array) * Extract the depth information from a byte array (image as array)
*
* @param data array of a jpeg image * @param data array of a jpeg image
* @return the value of Const.keyGDepthData (GDepth:Data) * @return the value of Const.keyGDepthData (GDepth:Data)
*/ */
@ -145,6 +147,7 @@ public class JPEGUtils extends Const
/** /**
* Extract the unblurred source image from a byte array (image as array) * Extract the unblurred source image from a byte array (image as array)
*
* @param data array of a jpeg image * @param data array of a jpeg image
* @return the value of Const.keyGImageData (GImage:Data) * @return the value of Const.keyGImageData (GImage:Data)
*/ */
@ -158,6 +161,7 @@ public class JPEGUtils extends Const
/** /**
* convert an integer into a two byte representation in base 256 * convert an integer into a two byte representation in base 256
*
* @param l integer * @param l integer
* @return byte array of length two containing two bytes representing l in base 256 * @return byte array of length two containing two bytes representing l in base 256
*/ */
@ -171,6 +175,7 @@ public class JPEGUtils extends Const
/** /**
* Get a block of data from an array containing blocks * Get a block of data from an array containing blocks
*
* @param data array of bytes * @param data array of bytes
* @param boundary position of the APP1 marker of the targeted block * @param boundary position of the APP1 marker of the targeted block
* @return the full targeted block * @return the full targeted block
@ -181,8 +186,7 @@ public class JPEGUtils extends Const
{ {
System.err.println("JPEGUtils getBlock: Block is no APP1-block"); System.err.println("JPEGUtils getBlock: Block is no APP1-block");
return data; return data;
} } else
else
{ {
return Arrays.copyOfRange(data, boundary, boundary+JPEGUtils.readBlockLength(boundary, data)+2); return Arrays.copyOfRange(data, boundary, boundary+JPEGUtils.readBlockLength(boundary, data)+2);
} }
@ -190,6 +194,7 @@ public class JPEGUtils extends Const
/** /**
* Same as getBlock, but returns the targeted block without the APP1 Marker and the two bytes containing the length of the block. * Same as getBlock, but returns the targeted block without the APP1 Marker and the two bytes containing the length of the block.
*
* @param data byte array * @param data byte array
* @param boundary position of the APP1 marker of the targeted block * @param boundary position of the APP1 marker of the targeted block
* @return the targeted block without the APP1 marker and the two bytes containing the length * @return the targeted block without the APP1 marker and the two bytes containing the length
@ -200,13 +205,14 @@ public class JPEGUtils extends Const
{ {
System.err.println("JPEGUtils getBlockWithoutAPP1: Block is no APP1-block"); System.err.println("JPEGUtils getBlockWithoutAPP1: Block is no APP1-block");
return null; return null;
} } else
else return Arrays.copyOfRange(data, boundary+4, boundary+JPEGUtils.readBlockLength(boundary,data)+2); return Arrays.copyOfRange(data, boundary+4, boundary+JPEGUtils.readBlockLength(boundary, data)+2);
} }
/** /**
* Same as getBlock but returns the block without any header (APP1, length, EXIF, StandardXMP, ExtendedXMP) * Same as getBlock but returns the block without any header (APP1, length, EXIF, StandardXMP, ExtendedXMP)
*
* @param data * @param data
* @param boundary * @param boundary
* @return * @return
@ -217,20 +223,24 @@ public class JPEGUtils extends Const
{ {
System.err.println("JPEGUtils getBlockWithoutHeader: Block is no APP1-block"); System.err.println("JPEGUtils getBlockWithoutHeader: Block is no APP1-block");
return null; return null;
} } else
else
{ {
int offset; int offset;
if(ArrayUtils.arrayIsPartOfOtherArrayOnOffset(data, markStandardXMP, 4+boundary)) offset = 2 + 2 + markStandardXMP.length; if (ArrayUtils.arrayIsPartOfOtherArrayOnOffset(data, markStandardXMP, 4+boundary))
else if(ArrayUtils.arrayIsPartOfOtherArrayOnOffset(data, markExtendedXMP, 4+boundary)) offset = 2 + 2 + markExtendedXMP.length + 32 + 4 + 4; offset = 2+2+markStandardXMP.length;
else if(ArrayUtils.arrayIsPartOfOtherArrayOnOffset(data, markEXIF, 4+boundary)) offset = 2 + 2 + markEXIF.length; else if (ArrayUtils.arrayIsPartOfOtherArrayOnOffset(data, markExtendedXMP, 4+boundary))
offset = 2+2+markExtendedXMP.length+32+4+4;
else if (ArrayUtils.arrayIsPartOfOtherArrayOnOffset(data, markEXIF, 4+boundary))
offset = 2+2+markEXIF.length;
else offset = 4; else offset = 4;
return Arrays.copyOfRange(data, boundary+offset, boundary+JPEGUtils.readBlockLength(boundary, data)+2); return Arrays.copyOfRange(data, boundary+offset, boundary+JPEGUtils.readBlockLength(boundary, data)+2);
} }
} }
/** /**
* Return all positions of "FF E1" in the byte[] * Return all positions of "FF E1" in the byte[]
* These are the bytes marking h´the beginning of a block * These are the bytes marking h´the beginning of a block
*
* @param data byte array * @param data byte array
* @return array of positions of APP1 marker * @return array of positions of APP1 marker
*/ */
@ -251,8 +261,10 @@ public class JPEGUtils extends Const
return out; return out;
} }
/** /**
* Return the exif block of the jpg. This is usually the first block of data, but this tolerates any position * Return the exif block of the jpg. This is usually the first block of data, but this tolerates any position
*
* @param data array of bytes * @param data array of bytes
* @return exif block * @return exif block
*/ */
@ -270,6 +282,7 @@ public class JPEGUtils extends Const
/** /**
* Gets the concatenated content of all ExtendedXMPBlocks in the data array without headers * Gets the concatenated content of all ExtendedXMPBlocks in the data array without headers
*
* @param data array * @param data array
* @return content of the blocks * @return content of the blocks
*/ */
@ -283,7 +296,8 @@ public class JPEGUtils extends Const
if (JPEGUtils.isExtendedXMP(block)) if (JPEGUtils.isExtendedXMP(block))
{ {
byte[] part = JPEGUtils.getBlockWithoutHeader(block, 0); byte[] part = JPEGUtils.getBlockWithoutHeader(block, 0);
if(part == null) System.err.println("JPEGUtils getExtendedXMPBlockContent part is null"); if (part == null)
System.err.println("JPEGUtils getExtendedXMPBlockContent part is null");
cont = ArrayUtils.concatenate(cont, part); cont = ArrayUtils.concatenate(cont, part);
} }
} }
@ -292,22 +306,27 @@ public class JPEGUtils extends Const
/** /**
* Returns the tail of the data array that is not content of any block. In case of the google camera this contains the JPEG image data * Returns the tail of the data array that is not content of any block. In case of the google camera this contains the JPEG image data
*
* @param data byte array * @param data byte array
* @return the trailing blockless imagedata * @return the trailing headless imagedata
*/ */
public static byte[] getImageTail(byte[] data) public static byte[] getImageTail(byte[] data)
{ {
byte[] out; byte[] out;
int[] bounds = getBoundaries(data); int[] bounds = getBoundaries(data);
if (bounds.length != 0)
{
int offset = 256 * (data[bounds[bounds.length-1]+2] & 0xFF)+(data[bounds[bounds.length-1]+3] & 0xFF); int offset = 256 * (data[bounds[bounds.length-1]+2] & 0xFF)+(data[bounds[bounds.length-1]+3] & 0xFF);
offset += bounds[bounds.length-1]; offset += bounds[bounds.length-1];
offset += 2; offset += 2;
out = Arrays.copyOfRange(data, offset, data.length); out = Arrays.copyOfRange(data, offset, data.length);
return out; return out;
} else return null;
} }
/** /**
* Returns the concatenated contents of all APP1 Blocks without the APP1 Markers * Returns the concatenated contents of all APP1 Blocks without the APP1 Markers
*
* @param data byte array * @param data byte array
* @return the concatenated contents of APP1 Blocks * @return the concatenated contents of APP1 Blocks
*/ */
@ -324,6 +343,7 @@ public class JPEGUtils extends Const
/** /**
* Returns the content of the StandardXMPBlock without header. * Returns the content of the StandardXMPBlock without header.
*
* @param data byte array * @param data byte array
* @return the content of the StandardXMPBlock * @return the content of the StandardXMPBlock
*/ */
@ -341,6 +361,7 @@ public class JPEGUtils extends Const
/** /**
* Returns the content of all XMPBlocks (StandardXMP + ExtendedXMP) without headers and concatenated. * Returns the content of all XMPBlocks (StandardXMP + ExtendedXMP) without headers and concatenated.
*
* @param data byte array * @param data byte array
* @return byte array with contents * @return byte array with contents
*/ */
@ -356,6 +377,7 @@ public class JPEGUtils extends Const
/** /**
* Returns true, if there is a APP1 marker at the given offset in data. * Returns true, if there is a APP1 marker at the given offset in data.
*
* @param data byte array * @param data byte array
* @param offset offset * @param offset offset
* @return true, if there is an APP1 marker at offset in data, else false * @return true, if there is an APP1 marker at offset in data, else false
@ -367,6 +389,7 @@ public class JPEGUtils extends Const
/** /**
* Returns true, if there is an Exif marker after the APP1 marker in this block. * Returns true, if there is an Exif marker after the APP1 marker in this block.
*
* @param block byte array * @param block byte array
* @return true, if there is an Exif marker at offset 4, else false. * @return true, if there is an Exif marker at offset 4, else false.
*/ */
@ -377,6 +400,7 @@ public class JPEGUtils extends Const
/** /**
* Returns true, if there is an Exif marker at offset in data, else false * Returns true, if there is an Exif marker at offset in data, else false
*
* @param data * @param data
* @param offset * @param offset
* @return true, if there is an Exif marker at offset in data, else false * @return true, if there is an Exif marker at offset in data, else false
@ -388,6 +412,7 @@ public class JPEGUtils extends Const
/** /**
* Returns true, if block is an extendedXMPBlock. * Returns true, if block is an extendedXMPBlock.
*
* @param block the block with FFE1 and the two following bytes * @param block the block with FFE1 and the two following bytes
* @return true, if there is an extendedXMPmarker at offset 4 in block, else false * @return true, if there is an extendedXMPmarker at offset 4 in block, else false
*/ */
@ -398,6 +423,7 @@ public class JPEGUtils extends Const
/** /**
* Returns true, if data has the magical bytes of a jpeg file at the start * Returns true, if data has the magical bytes of a jpeg file at the start
*
* @param data byte array * @param data byte array
* @return true, if there is a jpegmarker at offset 0 (FFD8) * @return true, if there is a jpegmarker at offset 0 (FFD8)
*/ */
@ -408,6 +434,7 @@ public class JPEGUtils extends Const
/** /**
* Returns true, if there is a jpegMarker at offset in data * Returns true, if there is a jpegMarker at offset in data
*
* @param data byte array * @param data byte array
* @param offset offset * @param offset offset
* @return true, if there is a jpegMarker at offset in data, ellse false * @return true, if there is a jpegMarker at offset in data, ellse false
@ -416,8 +443,10 @@ public class JPEGUtils extends Const
{ {
return ArrayUtils.arrayIsPartOfOtherArrayOnOffset(data, markJPG, offset); return ArrayUtils.arrayIsPartOfOtherArrayOnOffset(data, markJPG, offset);
} }
/** /**
* Returns true, if data is a standardXMPBlock * Returns true, if data is a standardXMPBlock
*
* @param block block with FFE1 and the two following bytes * @param block block with FFE1 and the two following bytes
* @return true, if the block is standardXMP (has standardXMP marker at offset 4), else false * @return true, if the block is standardXMP (has standardXMP marker at offset 4), else false
*/ */
@ -428,6 +457,7 @@ public class JPEGUtils extends Const
/** /**
* Returns true, if data has a standardXMPMarker at offset offset * Returns true, if data has a standardXMPMarker at offset offset
*
* @param block byte array * @param block byte array
* @return true, if the block has standardXMPMarker at offset, else false * @return true, if the block has standardXMPMarker at offset, else false
*/ */
@ -438,6 +468,7 @@ public class JPEGUtils extends Const
/** /**
* Replace the value of a key in data with another value value * Replace the value of a key in data with another value value
*
* @param data byte array * @param data byte array
* @param key key of the value that will be modified * @param key key of the value that will be modified
* @param value new value of key * @param value new value of key
@ -472,10 +503,8 @@ public class JPEGUtils extends Const
byte[] out = ArrayUtils.concatenate(pre, value); byte[] out = ArrayUtils.concatenate(pre, value);
out = ArrayUtils.concatenate(out, post); out = ArrayUtils.concatenate(out, post);
return out; return out;
} } else System.err.println("JPEGUtils replace: No closing \" found in data");
else System.err.println("JPEGUtils replace: No closing \" found in data"); } else System.err.println("JPEGUtils replace: key not found in data");
}
else System.err.println("JPEGUtils replace: key not found in data");
return null; return null;
} }
} }