Commented a lot

This commit is contained in:
vanitasvitae 2015-04-13 22:42:16 +02:00
parent 94993a907f
commit d7ccf6883c
6 changed files with 470 additions and 53 deletions

View file

@ -2,6 +2,13 @@
public class ArrayUtils public class ArrayUtils
{ {
/**
* Return true, if the array part is sub array of src from offset
* @param src array
* @param part array
* @param offset int
* @return true, if part is completely a subarray of src on offset offset, else false
*/
public static boolean arrayIsPartOfOtherArrayOnOffset(byte[] src, byte[] part, int offset) public static boolean arrayIsPartOfOtherArrayOnOffset(byte[] src, byte[] part, int offset)
{ {
if(offset<0 || part.length+offset > src.length) return false; if(offset<0 || part.length+offset > src.length) return false;
@ -12,6 +19,11 @@ public class ArrayUtils
return true; return true;
} }
/**
* converts the byte array b into a char array by casting all the bytes into chars
* @param b byte array
* @return char array
*/
public static char[] bytesToChars(byte[] b) public static char[] bytesToChars(byte[] b)
{ {
char[] c = new char[b.length]; char[] c = new char[b.length];
@ -20,6 +32,11 @@ public class ArrayUtils
return c; return c;
} }
/**
* converts the char array into a byte array by casting all the chars into bytes
* @param c char array
* @return byte array
*/
public static byte[] charsToBytes(char[] c) public static byte[] charsToBytes(char[] c)
{ {
byte[] b = new byte[c.length]; byte[] b = new byte[c.length];
@ -28,17 +45,28 @@ public class ArrayUtils
return b; return b;
} }
public static byte[] concatenate (byte[] out, byte[] bs) { /**
if(out == null) return bs; * concatenate two byte arrays
if(bs == null) return out; * @param first first byte array
int aLen = out.length; * @param second second byte array
int bLen = bs.length; * @return first + second
*/
public static byte[] concatenate (byte[] first, byte[] second) {
if(first == null) return second;
if(second == null) return first;
int aLen = first.length;
int bLen = second.length;
byte[] c = new byte[aLen+bLen]; byte[] c = new byte[aLen+bLen];
System.arraycopy(out, 0, c, 0, aLen); System.arraycopy(first, 0, c, 0, aLen);
System.arraycopy(bs, 0, c, aLen, bLen); System.arraycopy(second, 0, c, aLen, bLen);
return c; return c;
} }
/**
* convert an integer into a 32 bit byte array
* @param value integer
* @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),
@ -47,12 +75,16 @@ public class ArrayUtils
(byte)value}; (byte)value};
} }
/**
* convert a signed byte array into an unsigned byte array (sort of)
* @param b byte array of signed bytes
* @return byte array of unsigned bytes
*/
public static byte[] unsign(byte[] b) public static byte[] unsign(byte[] b)
{ {
byte[] u = new byte[b.length]; byte[] u = new byte[b.length];
for(int i=0; i<b.length; i++) for(int i=0; i<b.length; i++)
u[i] = (byte) (b[i]&0xFF); u[i] = (byte) (b[i]&0xFF);
return u; return u;
} }
} }

View file

@ -2,41 +2,87 @@
public class Const public class Const
{ {
/**
* Size of a chunk of data (used to split the ExtendedXMP information into blocks
*/
public static final int CHUNKSIZE = 65400; public static final int CHUNKSIZE = 65400;
/** 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";
public static final String EXTENDEDXMP = "ExtendedXMP"; public static final String EXTENDEDXMP = "ExtendedXMP";
public static byte[] markJPG = {(byte) 0xFF, (byte) 0xD8}; //FFD8 /**
* Magical bytes that identify the file as JPG (FF D8) (length: 2)
*/
public static byte[] markJPG = {(byte) 0xFF, (byte) 0xD8};
public static byte[] markAPP1 = {(byte) 0xFF, (byte) 0xE1}; //FFE1 /**
* Marker that defines the beginning of a new block of metadata (FF E1) (length: 2)
*/
public static byte[] markAPP1 = {(byte) 0xFF, (byte) 0xE1};
public static byte[] markEXIF = {(byte) 0x45, (byte) 0x78, (byte) 0x69, (byte) 0x66, (byte) 0x00, (byte) 0x00}; //EXIF\0\0 /**
* Header of the EXIF block (EXIF\0\0) (length: 6)
*/
public static byte[] markEXIF = {(byte) 0x45, (byte) 0x78, (byte) 0x69, (byte) 0x66, (byte) 0x00, (byte) 0x00};
public static byte[] markStandardXMP = { //Length: 29 /**
(byte) 0x68, (byte) 0x74, (byte) 0x74, (byte) 0x70, // http * Header of the StandardXMP block (http://ns.adobe.com/xap/1.0/\0) (length: 29)
(byte) 0x3A, (byte) 0x2F, (byte) 0x2F, (byte) 0x6E, (byte) 0x73, // ://ns */
(byte) 0x2E, (byte) 0x61, (byte) 0x64, (byte) 0x6F, (byte) 0x62, // .adob public static byte[] markStandardXMP = {
(byte) 0x65, (byte) 0x2E, (byte) 0x63, (byte) 0x6F, (byte) 0x6D, // e.com (byte) 0x68, (byte) 0x74, (byte) 0x74, (byte) 0x70,
(byte) 0x2F, (byte) 0x78, (byte) 0x61, (byte) 0x70, (byte) 0x2F, // /xap/ (byte) 0x3A, (byte) 0x2F, (byte) 0x2F, (byte) 0x6E, (byte) 0x73,
(byte) 0x31, (byte) 0x2E, (byte) 0x30, (byte) 0x2F, (byte) 0x00}; // 1.0/\0 (byte) 0x2E, (byte) 0x61, (byte) 0x64, (byte) 0x6F, (byte) 0x62,
(byte) 0x65, (byte) 0x2E, (byte) 0x63, (byte) 0x6F, (byte) 0x6D,
(byte) 0x2F, (byte) 0x78, (byte) 0x61, (byte) 0x70, (byte) 0x2F,
(byte) 0x31, (byte) 0x2E, (byte) 0x30, (byte) 0x2F, (byte) 0x00};
public static byte[] markExtendedXMP = { //Length: 35 /**
(byte) 0x68, (byte) 0x74, (byte) 0x74, (byte) 0x70, // http * Header of the ExtendedXMP block (http://ns.adobe.com/xmp/extension/\0) (length: 35)
(byte) 0x3A, (byte) 0x2F, (byte) 0x2F, (byte) 0x6E, (byte) 0x73, // ://ns */
(byte) 0x2E, (byte) 0x61, (byte) 0x64, (byte) 0x6F, (byte) 0x62, // .adob public static byte[] markExtendedXMP = {
(byte) 0x65, (byte) 0x2E, (byte) 0x63, (byte) 0x6F, (byte) 0x6D, // e.com (byte) 0x68, (byte) 0x74, (byte) 0x74, (byte) 0x70,
(byte) 0x2F, (byte) 0x78, (byte) 0x6D, (byte) 0x70, (byte) 0x2F, // /xmp/ (byte) 0x3A, (byte) 0x2F, (byte) 0x2F, (byte) 0x6E, (byte) 0x73,
(byte) 0x65, (byte) 0x78, (byte) 0x74, (byte) 0x65, (byte) 0x6e, // exten (byte) 0x2E, (byte) 0x61, (byte) 0x64, (byte) 0x6F, (byte) 0x62,
(byte) 0x73, (byte) 0x69, (byte) 0x6f, (byte) 0x6e, (byte) 0x2f, // sion/ (byte) 0x65, (byte) 0x2E, (byte) 0x63, (byte) 0x6F, (byte) 0x6D,
(byte) 0x00}; // \0 (byte) 0x2F, (byte) 0x78, (byte) 0x6D, (byte) 0x70, (byte) 0x2F,
(byte) 0x65, (byte) 0x78, (byte) 0x74, (byte) 0x65, (byte) 0x6e,
(byte) 0x73, (byte) 0x69, (byte) 0x6f, (byte) 0x6e, (byte) 0x2f,
(byte) 0x00};
public static byte[] keyHasExtendedXMP = { //xmpNote:HasExtendedXMP /** Keys (following scheme: key="value") */
0x78,0x6d,0x70,0x4e,0x6f,0x74,0x65,0x3a,0x48,0x61,0x73,0x45,0x78,0x74,0x65,0x6e,0x64,0x65,0x64,0x58,0x4d,0x50
/**
* Key for the MD5 digest of the full ExtendedXMP block content (without headers etc.)
* (xmpNote:HasExtendedXMP) (length: 22)
*/
public static byte[] keyHasExtendedXMP = {
(byte) 0x78,(byte) 0x6d,(byte) 0x70,(byte) 0x4e,(byte) 0x6f,
(byte) 0x74,(byte) 0x65,(byte) 0x3a,(byte) 0x48,(byte) 0x61,
(byte) 0x73,(byte) 0x45,(byte) 0x78,(byte) 0x74,(byte) 0x65,
(byte) 0x6e,(byte) 0x64,(byte) 0x65,(byte) 0x64,(byte) 0x58,
(byte) 0x4d,(byte) 0x50
}; };
public static byte[] keyGDepthData = {0x47,0x44,0x65,0x70,0x74,0x68,0x3a,0x44,0x61,0x74,0x61}; //GDepth:Data /**
* Key for the Base64 encoded png image containing the depth information
* (GDepth:Data) (length: 11)
*/
public static byte[] keyGDepthData = {
(byte) 0x47,(byte) 0x44,(byte) 0x65,(byte) 0x70,(byte) 0x74,
(byte) 0x68,(byte) 0x3a,(byte) 0x44,(byte) 0x61,(byte) 0x74,
(byte) 0x61
};
public static byte[] keyGImageData = {0x47,0x49,0x6d,0x61,0x67,0x65,0x3a,0x44,0x61,0x74,0x61}; //GImage:Data /**
* Key for the Base64 encoded unblurred jpg source image (Google camera will combine this with the depth information to render the blurred image)
* (GImage:Data) (length: 11)
*/
public static byte[] keyGImageData = {
(byte) 0x47,(byte) 0x49,(byte) 0x6d,(byte) 0x61,(byte) 0x67,
(byte) 0x65,(byte) 0x3a,(byte) 0x44,(byte) 0x61,(byte) 0x74,
(byte) 0x61
};
} }

View file

@ -2,9 +2,16 @@
public class DepthMapNeedle public class DepthMapNeedle
{ {
/**
* Interprete the arguments and execute the programm
* @param args
*/
public static void main(String[] args) public static void main(String[] args)
{ {
//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
if(args.length >= 2 && args[0].equals("-e")) if(args.length >= 2 && args[0].equals("-e"))
{ {
for(int i=1; i<args.length; i++) for(int i=1; i<args.length; i++)
@ -14,6 +21,8 @@ public class DepthMapNeedle
else System.err.println("There is no Depthmap in file "+args[i]); else System.err.println("There is no Depthmap in file "+args[i]);
} }
} }
//export source image
else if(args.length >=2 && args[0].equals("-s")) else if(args.length >=2 && args[0].equals("-s"))
{ {
for(int i=1; i<args.length; i++) for(int i=1; i<args.length; i++)
@ -23,6 +32,8 @@ public class DepthMapNeedle
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?"); 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
else if(args.length >= 3 && args[0].equals("-i")) else if(args.length >= 3 && args[0].equals("-i"))
{ {
String depthmap = args[1]; String depthmap = args[1];
@ -36,6 +47,9 @@ public class DepthMapNeedle
} }
} }
/**
* Show help text
*/
public static void help() public static void help()
{ {
System.out.println("Welcome to DepthMapNeedle!" System.out.println("Welcome to DepthMapNeedle!"

View file

@ -5,12 +5,21 @@ import java.security.NoSuchAlgorithmException;
public class HexUtil public class HexUtil
{ {
/**
* Print out a byte array in hexadecimal
* @param bytes array
*/
public static void printHex(byte[] bytes) public static void printHex(byte[] bytes)
{ {
for(byte b : bytes) System.out.print(byteToHex(b)); for(byte b : bytes) System.out.print(byteToHexString(b));
} }
public static String byteToHex(byte data) { /**
* convert a byte to a hex string
* @param data array
* @return String representation in hex
*/
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));
@ -19,6 +28,11 @@ public class HexUtil
return buf.toString(); return buf.toString();
} }
/**
* convert a integer into a hexadecimal character
* @param i integer
* @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);
@ -27,6 +41,11 @@ public class HexUtil
} }
} }
/**
* Generate the md5 digest of the byte array data in hexadecimal
* @param data array
* @return byte array of the md5 digest in hex
*/
public static byte[] generateMD5(byte[] data) public static byte[] generateMD5(byte[] data)
{ {
try try
@ -35,7 +54,7 @@ public class HexUtil
String m=""; String m="";
for(int i=0; i<md5.length; i++) for(int i=0; i<md5.length; i++)
{ {
m = m + byteToHex(md5[i]).toUpperCase(); m = m + byteToHexString(md5[i]).toUpperCase();
} }
return m.getBytes(); return m.getBytes();
} catch (NoSuchAlgorithmException e) } catch (NoSuchAlgorithmException e)

View file

@ -1,27 +1,65 @@
import java.io.File; import java.io.File;
import java.util.Base64; import java.util.Base64;
public class JPEG extends Const public class JPEG extends Const
{ {
/** MEMBERS */
/**
* location of the source file
*/
private String filename; private String filename;
/**
* byte array containing the actual file
*/
private byte[] rawData; private byte[] rawData;
/**
* byte array containing EXIF block information without header
*/
private byte[] exif; private byte[] exif;
/**
* byte array containing StandardXMP block information without header
*/
private byte[] xmpSta; private byte[] xmpSta;
/**
* byte array containing ExtendedXMP block information without header
*/
private byte[] xmpExt; private byte[] xmpExt;
/**
* byte array conatining the trailing headless data block conatining the blurred jpeg image
*/
private byte[] tail; private byte[] tail;
/** METHODS */
/**
* Constructor
* @param rawData
*/
public JPEG(byte[] rawData) public JPEG(byte[] rawData)
{ {
this.init(rawData, null); this.init(rawData, null);
} }
/**
* Constructor, that reads a byte array from file
* @param filename
*/
public JPEG(String filename) public JPEG(String filename)
{ {
this.init(IO.read(new File(filename)), filename); this.init(IO.read(new File(filename)), filename);
} }
/**
* Disassemble the rawData byte array into sub arrays
*/
public void disassemble() public void disassemble()
{ {
exif = this.getEXIFBlock(); exif = this.getEXIFBlock();
@ -30,6 +68,10 @@ public class JPEG extends Const
tail = this.getImageTail(); tail = this.getImageTail();
} }
/**
* extract the depthmap from the jpeg and store it in the same location as the jpeg itself but with the suffix _d.png
* @return
*/
public boolean exportDepthMap() public boolean exportDepthMap()
{ {
String out = filename; String out = filename;
@ -37,6 +79,11 @@ public class JPEG extends Const
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"
* @param file String of the output location
* @return success
*/
public boolean exportDepthMap(String file) public boolean exportDepthMap(String file)
{ {
byte[] b64 = ArrayUtils.unsign(extractDepthMap()); byte[] b64 = ArrayUtils.unsign(extractDepthMap());
@ -49,6 +96,10 @@ public class JPEG extends Const
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"
* @return success
*/
public boolean exportSourceImage() public boolean exportSourceImage()
{ {
String out = filename; String out = filename;
@ -56,6 +107,11 @@ public class JPEG extends Const
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"
* @param file String of the location
* @return success
*/
public boolean exportSourceImage(String file) public boolean exportSourceImage(String file)
{ {
byte[] b64 = ArrayUtils.unsign(extractSourceImage()); byte[] b64 = ArrayUtils.unsign(extractSourceImage());
@ -68,80 +124,148 @@ public class JPEG extends Const
else return false; else return false;
} }
/**
* Extract and return the depthmap information
* @return depthmap as byte array
*/
public byte[] extractDepthMap() public byte[] extractDepthMap()
{ {
return JPEGUtils.extractDepthMap(rawData); return JPEGUtils.extractDepthMap(rawData);
} }
/**
* Extract and return the unblurred source image
* @return source image as byte array
*/
public byte[] extractSourceImage() public byte[] extractSourceImage()
{ {
return JPEGUtils.extractSourceImage(rawData); return JPEGUtils.extractSourceImage(rawData);
} }
/**
* 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
*/
public int[] getBoundaries() public int[] getBoundaries()
{ {
return JPEGUtils.getBoundaries(rawData); return JPEGUtils.getBoundaries(rawData);
} }
/**
* return the exif block of the jpeg as a byte array
* @return exifblock (member)
*/
public byte[] getExif() public byte[] getExif()
{ {
return exif; return exif;
} }
/**
* Gets the exif block from the rawData array instead from the member
* @return the exif block from rawData
*/
public byte[] getEXIFBlock() public byte[] getEXIFBlock()
{ {
return JPEGUtils.getEXIFBlock(rawData); return JPEGUtils.getEXIFBlock(rawData);
} }
/**
* Extract and return the content of all the ExtendedXMP blocks concatenated.
* @return content of the ExtendedXMP blocks
*/
public byte[] getExtendedXMPContent() public byte[] getExtendedXMPContent()
{ {
return JPEGUtils.getExtendedXMPBlockContent(rawData); return JPEGUtils.getExtendedXMPBlockContent(rawData);
} }
/**
* return the filename and path
* @return
*/
public String getFilename() public String getFilename()
{ {
return filename; return filename;
} }
/**
* Return the headless block of data in the image (this contains the JPEG you see when opening the file)
* @return headless JPEG tail
*/
public byte[] getImageTail() public byte[] getImageTail()
{ {
return JPEGUtils.getImageTail(rawData); return JPEGUtils.getImageTail(rawData);
} }
/**
* Return the Metadata of the image (content of all APP1 marked blocks)
* @return metadata
*/
public byte[] getMetadata() public byte[] getMetadata()
{ {
return JPEGUtils.getMetadata(rawData); return JPEGUtils.getMetadata(rawData);
} }
/**
* Returns the byte array that IO.read(image) returned
* @return byte array of the image file
*/
public byte[] getRawData() public byte[] getRawData()
{ {
return this.rawData; return this.rawData;
} }
/**
* Extracts and returns the content of the StandardXMP block
* @return content of the StandardXMP block
*/
public byte[] getStandardXMPBlockContent() public byte[] getStandardXMPBlockContent()
{ {
return JPEGUtils.getStandardXMPBlockContent(rawData); return JPEGUtils.getStandardXMPBlockContent(rawData);
} }
/**
* Returns the Image tail (member)
* @return member image tail
*/
public byte[] getTail() public byte[] getTail()
{ {
return tail; return tail;
} }
/**
* Extracts and returns the content of all XMP blocks concatenated (StandardXMP + ExtendedXMPs)
* @return content of the XMP blocks
*/
public byte[] getXMPBlocksContent() public byte[] getXMPBlocksContent()
{ {
return JPEGUtils.getXMPBlocksContent(rawData); return JPEGUtils.getXMPBlocksContent(rawData);
} }
/**
* Return the member containing the ExtendedXMP information
* @return XmpExt (member)
*/
public byte[] getXmpExt() public byte[] getXmpExt()
{ {
return xmpExt; return xmpExt;
} }
/**
* Return the member containing the StandardXMP information
* @return xmpSta (member)
*/
public byte[] getXmpSta() public byte[] getXmpSta()
{ {
return xmpSta; return xmpSta;
} }
/**
* initializes the JPEG object.
* set the array containing the image as member rawData, set filename
* disassemble the Image and set the other members with content.
* @param raw byte array of the image
* @param filename location
*/
private void init(byte[] raw, String filename) private void init(byte[] raw, String filename)
{ {
this.rawData = raw; this.rawData = raw;
@ -149,6 +273,11 @@ public class JPEG extends Const
this.disassemble(); this.disassemble();
} }
/**
* 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
* @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.getEncoder().encode(IO.read(new File(filename)));
@ -157,6 +286,11 @@ public class JPEG extends Const
else return false; else return false;
} }
/**
* Reassemble a functional jpeg byte array from block data.
* Set rawData to the new array and also return it
* @return reassembled jpeg byte array
*/
public byte[] reassemble() public byte[] reassemble()
{ {
byte[] md5 = HexUtil.generateMD5(xmpExt); byte[] md5 = HexUtil.generateMD5(xmpExt);
@ -171,30 +305,54 @@ public class JPEG extends Const
return out; return out;
} }
/**
* Write the image to disk (location is defined in filename)
*/
public void save() public void save()
{ {
this.save(filename); this.save(filename);
} }
/**
* Write the image to the file specified in "file"
* @param file
*/
public void save(String file) public void save(String file)
{ {
IO.write(this.reassemble(), file); IO.write(this.reassemble(), file);
} }
/**
* Set the exif memberto a new byte array
* @param exif new array
*/
public void setExif(byte[] exif) public void setExif(byte[] exif)
{ {
this.exif=exif; this.exif=exif;
} }
/**
* Set the tail member array to a new byte array
* @param tail new array
*/
public void setTail(byte[] tail) public void setTail(byte[] tail)
{ {
this.tail=tail; this.tail=tail;
} }
/**
* set the xmpExt member array to a new byte array
* @param xmpExt new array
*/
public void setXmpExt(byte[] xmpExt) public void setXmpExt(byte[] xmpExt)
{ {
this.xmpExt=xmpExt; this.xmpExt=xmpExt;
} }
/**
* set the xmpSta member array to a new byte array
* @param xmpSta new array
*/
public void setXmpSta(byte[] xmpSta) public void setXmpSta(byte[] xmpSta)
{ {
this.xmpSta=xmpSta; this.xmpSta=xmpSta;

View file

@ -5,47 +5,69 @@ import java.util.Vector;
public class JPEGUtils extends Const public class JPEGUtils extends Const
{ {
/**
public static int blockLength(int boundaryPos, byte[] data) * 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 data byte array containing the block WITH APP1 Marker
* @return length of the block
*/
public static int readBlockLength(int boundaryPos, byte[] data)
{ {
//Check whether entered position is APP1 Marker
if(!JPEGUtils.isAPP1Marker(data,boundaryPos)) if(!JPEGUtils.isAPP1Marker(data,boundaryPos))
{ {
System.err.println("JPEGUtils blockLength: Block is no APP1 Block!"); System.err.println("JPEGUtils blockLength: Block is no APP1 Block!");
return -1; return -1;
} }
int o; int o;
//Read length
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 got ArrayIndexOutOfBoundsException:"); System.err.println("JPEGUtils blockLength threw ArrayIndexOutOfBoundsException. Maybe the block is cut after APP1 Marker?");
e.printStackTrace(); e.printStackTrace();
return -1; return -1;
} }
return o; return o;
} }
/**
* decorate information with a block header of a certain type. The type is specified via the argument type.
* In case, type == EXTENDEDXMP and data.length > CHUNKSIZE multiple ExtendedXMPBlocks will be generated and concatenated.
* Available types:
* Const.EXIF, Const.STANDARDXMP, Const.EXTENDEDXMP
* @param data byte array of data that will be decorated
* @param type String declaring the type of header for the block.
* @return decorated block
*/
public static byte[] decorateBlock(byte[] data, String type) public static byte[] decorateBlock(byte[] data, String type)
{ {
//EXIF Block: 'APP1 + BLOCKLENGTH + EXIF\0\0 + data'
if(type.equals(EXIF)) if(type.equals(EXIF))
{ {
data = ArrayUtils.concatenate(markEXIF, data); data = ArrayUtils.concatenate(markEXIF, data);
byte[] pre = ArrayUtils.concatenate(markAPP1, genLen(data.length+2)); byte[] pre = ArrayUtils.concatenate(markAPP1, genLen(data.length+2));
return ArrayUtils.concatenate(pre, data); return ArrayUtils.concatenate(pre, data);
} }
//StandardXMP: 'APP1 + BLOCKLENGTH + http://ns.adobe.com/xap/1.0/\0 + data'
else if(type.equals(STANDARDXMP)) else if(type.equals(STANDARDXMP))
{ {
data = ArrayUtils.concatenate(markStandardXMP, data); data = ArrayUtils.concatenate(markStandardXMP, data);
byte[] pre = ArrayUtils.concatenate(markAPP1, genLen(data.length+2)); byte[] pre = ArrayUtils.concatenate(markAPP1, genLen(data.length+2));
return ArrayUtils.concatenate(pre,data); return ArrayUtils.concatenate(pre,data);
} }
//ExtendedXMP: 'APP1 + BLOCKLENGTH + http://ns.adobe.com/xmp/extension/\0 + MD5 + EXTENDEDLENGTH + EXTENDEDOFFSET + DATAPORTION
else if(type.equals(EXTENDEDXMP)) else if(type.equals(EXTENDEDXMP))
{ {
byte[] out = new byte[0]; byte[] out = new byte[0];
//MD5 checksum is digest of the datacontent
byte[] md5 = HexUtil.generateMD5(data); byte[] md5 = HexUtil.generateMD5(data);
int i=0; int i=0;
int blockCount = data.length/CHUNKSIZE; int blockCount = data.length/CHUNKSIZE;
//decorate blockportions of size CHUNKSIZE
while (i<blockCount) while (i<blockCount)
{ {
byte[] part = Arrays.copyOfRange(data, i*CHUNKSIZE, (i+1)*CHUNKSIZE); byte[] part = Arrays.copyOfRange(data, i*CHUNKSIZE, (i+1)*CHUNKSIZE);
@ -59,7 +81,7 @@ public class JPEGUtils extends Const
out = ArrayUtils.concatenate(out, part); out = ArrayUtils.concatenate(out, part);
i++; i++;
} }
//Rest //Decorate the restportion < CHUNKSIZE
byte[] part = Arrays.copyOfRange(data, i*CHUNKSIZE, data.length); byte[] part = Arrays.copyOfRange(data, i*CHUNKSIZE, data.length);
byte[] pre = markAPP1; byte[] pre = markAPP1;
pre = ArrayUtils.concatenate(pre, genLen(2+markExtendedXMP.length + 32 + 4 + 4 + part.length)); pre = ArrayUtils.concatenate(pre, genLen(2+markExtendedXMP.length + 32 + 4 + 4 + part.length));
@ -71,8 +93,18 @@ public class JPEGUtils extends Const
out = ArrayUtils.concatenate(out, part); out = ArrayUtils.concatenate(out, part);
return out; return out;
} }
System.err.println("JPEGUtils decorateBlock no valid type entered.");
return null; return null;
} }
/**
* Extract the value of a key from the data Array.
* For Example: data = 'blabalkey="Hallo"blabla', key = 'key'
* Output of extract(data, key) will be 'Hallo'
* @param data array of bytes
* @param key array that contains the key
* @return the "value" of the key (the part after the key and the following '="' to the next '"'.
*/
public static byte[] extract(byte[] data, byte[] key) public static byte[] extract(byte[] data, byte[] key)
{ {
int start = -1; int start = -1;
@ -97,6 +129,12 @@ public class JPEGUtils extends Const
System.err.println("JPEGUtils extract found start for \""+new String(key) +"\": false"); System.err.println("JPEGUtils extract found start for \""+new String(key) +"\": false");
return null; return null;
} }
/**
* Extract the depth information from a byte array (image as array)
* @param data array of a jpeg image
* @return the value of Const.keyGDepthData (GDepth:Data)
*/
public static byte[] extractDepthMap(byte[] data) public static byte[] extractDepthMap(byte[] data)
{ {
byte[] meta = getXMPBlocksContent(data); byte[] meta = getXMPBlocksContent(data);
@ -104,6 +142,12 @@ public class JPEGUtils extends Const
if(depth == null) System.err.println("JPEGUtils extractDepthMap is null"); if(depth == null) System.err.println("JPEGUtils extractDepthMap is null");
return depth; return depth;
} }
/**
* Extract the unblurred source image from a byte array (image as array)
* @param data array of a jpeg image
* @return the value of Const.keyGImageData (GImage:Data)
*/
public static byte[] extractSourceImage(byte[] data) public static byte[] extractSourceImage(byte[] data)
{ {
byte[] meta = getXMPBlocksContent(data); byte[] meta = getXMPBlocksContent(data);
@ -111,6 +155,12 @@ public class JPEGUtils extends Const
if(src == null) System.err.println("JPEGUtils extractSourceImage is null"); if(src == null) System.err.println("JPEGUtils extractSourceImage is null");
return src; return src;
} }
/**
* convert an integer into a two byte representation in base 256
* @param l integer
* @return byte array of length two containing two bytes representing l in base 256
*/
public static byte[] genLen(int l) public static byte[] genLen(int l)
{ {
byte[] o = new byte[2]; byte[] o = new byte[2];
@ -118,6 +168,13 @@ public class JPEGUtils extends Const
o[1] = (byte) (l%256); o[1] = (byte) (l%256);
return o; return o;
} }
/**
* Get a block of data from an array containing blocks
* @param data array of bytes
* @param boundary position of the APP1 marker of the targeted block
* @return the full targeted block
*/
public static byte[] getBlock(byte[] data, int boundary) public static byte[] getBlock(byte[] data, int boundary)
{ {
if(!JPEGUtils.isAPP1Marker(data,boundary)) if(!JPEGUtils.isAPP1Marker(data,boundary))
@ -127,9 +184,16 @@ public class JPEGUtils extends Const
} }
else else
{ {
return Arrays.copyOfRange(data, boundary, boundary+JPEGUtils.blockLength(boundary, data)+2); return Arrays.copyOfRange(data, boundary, boundary+JPEGUtils.readBlockLength(boundary, data)+2);
} }
} }
/**
* 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 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
*/
public static byte[] getBlockWithoutAPP1(byte[] data, int boundary) public static byte[] getBlockWithoutAPP1(byte[] data, int boundary)
{ {
if(!JPEGUtils.isAPP1Marker(data,boundary)) if(!JPEGUtils.isAPP1Marker(data,boundary))
@ -137,9 +201,16 @@ 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 return Arrays.copyOfRange(data, boundary+4, boundary+JPEGUtils.blockLength(boundary,data)+2); else 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)
* @param data
* @param boundary
* @return
*/
public static byte[] getBlockWithoutHeader(byte[] data, int boundary) public static byte[] getBlockWithoutHeader(byte[] data, int boundary)
{ {
if(!JPEGUtils.isAPP1Marker(data,boundary)) if(!JPEGUtils.isAPP1Marker(data,boundary))
@ -154,13 +225,14 @@ public class JPEGUtils extends Const
else if(ArrayUtils.arrayIsPartOfOtherArrayOnOffset(data, markExtendedXMP, 4+boundary)) offset = 2 + 2 + markExtendedXMP.length + 32 + 4 + 4; 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 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.blockLength(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
* @param data byte array * @param data byte array
* @return array of positions * @return array of positions of APP1 marker
*/ */
public static int[] getBoundaries(byte[] data) public static int[] getBoundaries(byte[] data)
{ {
@ -180,8 +252,8 @@ public class JPEGUtils extends Const
} }
/** /**
* Return the exif block of the jpg. This is usually the first block of data * Return the exif block of the jpg. This is usually the first block of data, but this tolerates any position
* @param data * @param data array of bytes
* @return exif block * @return exif block
*/ */
public static byte[] getEXIFBlock(byte[] data) public static byte[] getEXIFBlock(byte[] data)
@ -195,6 +267,12 @@ public class JPEGUtils extends Const
System.err.println("JPEGUtils getEXIFBlock: No EXIF-block found"); System.err.println("JPEGUtils getEXIFBlock: No EXIF-block found");
return null; return null;
} }
/**
* Gets the concatenated content of all ExtendedXMPBlocks in the data array without headers
* @param data array
* @return content of the blocks
*/
public static byte[] getExtendedXMPBlockContent(byte[] data) public static byte[] getExtendedXMPBlockContent(byte[] data)
{ {
int[] bounds = JPEGUtils.getBoundaries(data); int[] bounds = JPEGUtils.getBoundaries(data);
@ -212,6 +290,11 @@ public class JPEGUtils extends Const
return cont; return cont;
} }
/**
* 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
* @return the trailing blockless imagedata
*/
public static byte[] getImageTail(byte[] data) public static byte[] getImageTail(byte[] data)
{ {
byte[] out; byte[] out;
@ -222,6 +305,12 @@ public class JPEGUtils extends Const
out = Arrays.copyOfRange(data, offset, data.length); out = Arrays.copyOfRange(data, offset, data.length);
return out; return out;
} }
/**
* Returns the concatenated contents of all APP1 Blocks without the APP1 Markers
* @param data byte array
* @return the concatenated contents of APP1 Blocks
*/
public static byte[] getMetadata(byte[] data) public static byte[] getMetadata(byte[] data)
{ {
int[] boundaries = getBoundaries(data); int[] boundaries = getBoundaries(data);
@ -232,6 +321,12 @@ public class JPEGUtils extends Const
} }
return out; return out;
} }
/**
* Returns the content of the StandardXMPBlock without header.
* @param data byte array
* @return the content of the StandardXMPBlock
*/
public static byte[] getStandardXMPBlockContent(byte[] data) public static byte[] getStandardXMPBlockContent(byte[] data)
{ {
int[] bounds = JPEGUtils.getBoundaries(data); int[] bounds = JPEGUtils.getBoundaries(data);
@ -243,6 +338,12 @@ public class JPEGUtils extends Const
System.err.println("JPEGUtils getStandardXMPBlockContent is null"); System.err.println("JPEGUtils getStandardXMPBlockContent is null");
return null; return null;
} }
/**
* Returns the content of all XMPBlocks (StandardXMP + ExtendedXMP) without headers and concatenated.
* @param data byte array
* @return byte array with contents
*/
public static byte[] getXMPBlocksContent(byte[] data) public static byte[] getXMPBlocksContent(byte[] data)
{ {
byte[] stand = getStandardXMPBlockContent(data); byte[] stand = getStandardXMPBlockContent(data);
@ -252,48 +353,96 @@ public class JPEGUtils extends Const
if(out==null) System.err.println("JPEGUtils getXMPBlocksContent is null"); if(out==null) System.err.println("JPEGUtils getXMPBlocksContent is null");
return out; return out;
} }
/**
* Returns true, if there is a APP1 marker at the given offset in data.
* @param data byte array
* @param offset offset
* @return true, if there is an APP1 marker at offset in data, else false
*/
public static boolean isAPP1Marker(byte[] data, int offset) public static boolean isAPP1Marker(byte[] data, int offset)
{ {
return ArrayUtils.arrayIsPartOfOtherArrayOnOffset(data, markAPP1, offset); return ArrayUtils.arrayIsPartOfOtherArrayOnOffset(data, markAPP1, offset);
} }
/**
* Returns true, if there is an Exif marker after the APP1 marker in this block.
* @param block byte array
* @return true, if there is an Exif marker at offset 4, else false.
*/
public static boolean isEXIFBlock(byte[] block) public static boolean isEXIFBlock(byte[] block)
{ {
return JPEGUtils.isEXIFMarker(block, 4); return JPEGUtils.isEXIFMarker(block, 4);
} }
/**
* Returns true, if there is an Exif marker at offset in data, else false
* @param data
* @param offset
* @return true, if there is an Exif marker at offset in data, else false
*/
public static boolean isEXIFMarker(byte[] data, int offset) public static boolean isEXIFMarker(byte[] data, int offset)
{ {
return ArrayUtils.arrayIsPartOfOtherArrayOnOffset(data, markEXIF, offset); return ArrayUtils.arrayIsPartOfOtherArrayOnOffset(data, markEXIF, offset);
} }
/** /**
* Block is the block with FFE1 and the two following bytes * Returns true, if block is an extendedXMPBlock.
* @param block block * @param block the block with FFE1 and the two following bytes
* @return true, if the block is extended xmp * @return true, if there is an extendedXMPmarker at offset 4 in block, else false
*/ */
public static boolean isExtendedXMP(byte[] block) public static boolean isExtendedXMP(byte[] block)
{ {
return ArrayUtils.arrayIsPartOfOtherArrayOnOffset(block, markExtendedXMP, 4); return ArrayUtils.arrayIsPartOfOtherArrayOnOffset(block, markExtendedXMP, 4);
} }
/**
* Returns true, if data has the magical bytes of a jpeg file at the start
* @param data byte array
* @return true, if there is a jpegmarker at offset 0 (FFD8)
*/
public static boolean isJPG(byte[] data) public static boolean isJPG(byte[] data)
{ {
return JPEGUtils.isJPG(data, 0); return JPEGUtils.isJPG(data, 0);
} }
/**
* Returns true, if there is a jpegMarker at offset in data
* @param data byte array
* @param offset offset
* @return true, if there is a jpegMarker at offset in data, ellse false
*/
public static boolean isJPG(byte[] data, int offset) public static boolean isJPG(byte[] data, int offset)
{ {
return ArrayUtils.arrayIsPartOfOtherArrayOnOffset(data, markJPG, offset); return ArrayUtils.arrayIsPartOfOtherArrayOnOffset(data, markJPG, offset);
} }
/** /**
* Block is the block with FFE1 and the two following bytes * Returns true, if data is a standardXMPBlock
* @param block block * @param block block with FFE1 and the two following bytes
* @return true, if the block is standard xmp * @return true, if the block is standardXMP (has standardXMP marker at offset 4), else false
*/ */
public static boolean isStandardXMP(byte[] block) public static boolean isStandardXMP(byte[] block)
{ {
return ArrayUtils.arrayIsPartOfOtherArrayOnOffset(block, markStandardXMP, 4); return ArrayUtils.arrayIsPartOfOtherArrayOnOffset(block, markStandardXMP, 4);
} }
/**
* Returns true, if data has a standardXMPMarker at offset offset
* @param block byte array
* @return true, if the block has standardXMPMarker at offset, else false
*/
public static boolean isStandardXMP(byte[] block, int offset) public static boolean isStandardXMP(byte[] block, int offset)
{ {
return ArrayUtils.arrayIsPartOfOtherArrayOnOffset(block, markStandardXMP, offset); return ArrayUtils.arrayIsPartOfOtherArrayOnOffset(block, markStandardXMP, offset);
} }
/**
* Replace the value of a key in data with another value value
* @param data byte array
* @param key key of the value that will be modified
* @param value new value of key
* @return modified data byte array
*/
public static byte[] replace(byte[] data, byte[] key, byte[] value) public static byte[] replace(byte[] data, byte[] key, byte[] value)
{ {
int start = -1; int start = -1;
@ -327,7 +476,6 @@ public class JPEGUtils extends Const
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;
} }
} }