Commented a lot
This commit is contained in:
parent
94993a907f
commit
d7ccf6883c
6 changed files with 470 additions and 53 deletions
|
@ -5,47 +5,69 @@ import java.util.Vector;
|
|||
|
||||
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))
|
||||
{
|
||||
System.err.println("JPEGUtils blockLength: Block is no APP1 Block!");
|
||||
return -1;
|
||||
}
|
||||
int o;
|
||||
//Read length
|
||||
try
|
||||
{
|
||||
o = 256*(data[boundaryPos+2]&0xFF) + (data[boundaryPos+3]&0xFF);
|
||||
}
|
||||
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();
|
||||
return -1;
|
||||
}
|
||||
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)
|
||||
{
|
||||
//EXIF Block: 'APP1 + BLOCKLENGTH + EXIF\0\0 + data'
|
||||
if(type.equals(EXIF))
|
||||
{
|
||||
data = ArrayUtils.concatenate(markEXIF, data);
|
||||
byte[] pre = ArrayUtils.concatenate(markAPP1, genLen(data.length+2));
|
||||
return ArrayUtils.concatenate(pre, data);
|
||||
}
|
||||
//StandardXMP: 'APP1 + BLOCKLENGTH + http://ns.adobe.com/xap/1.0/\0 + data'
|
||||
else if(type.equals(STANDARDXMP))
|
||||
{
|
||||
data = ArrayUtils.concatenate(markStandardXMP, data);
|
||||
byte[] pre = ArrayUtils.concatenate(markAPP1, genLen(data.length+2));
|
||||
return ArrayUtils.concatenate(pre,data);
|
||||
}
|
||||
//ExtendedXMP: 'APP1 + BLOCKLENGTH + http://ns.adobe.com/xmp/extension/\0 + MD5 + EXTENDEDLENGTH + EXTENDEDOFFSET + DATAPORTION
|
||||
else if(type.equals(EXTENDEDXMP))
|
||||
{
|
||||
byte[] out = new byte[0];
|
||||
//MD5 checksum is digest of the datacontent
|
||||
byte[] md5 = HexUtil.generateMD5(data);
|
||||
int i=0;
|
||||
int blockCount = data.length/CHUNKSIZE;
|
||||
//decorate blockportions of size CHUNKSIZE
|
||||
while (i<blockCount)
|
||||
{
|
||||
byte[] part = Arrays.copyOfRange(data, i*CHUNKSIZE, (i+1)*CHUNKSIZE);
|
||||
|
@ -59,7 +81,7 @@ public class JPEGUtils extends Const
|
|||
out = ArrayUtils.concatenate(out, part);
|
||||
i++;
|
||||
}
|
||||
//Rest
|
||||
//Decorate the restportion < CHUNKSIZE
|
||||
byte[] part = Arrays.copyOfRange(data, i*CHUNKSIZE, data.length);
|
||||
byte[] pre = markAPP1;
|
||||
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);
|
||||
return out;
|
||||
}
|
||||
System.err.println("JPEGUtils decorateBlock no valid type entered.");
|
||||
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)
|
||||
{
|
||||
int start = -1;
|
||||
|
@ -97,6 +129,12 @@ public class JPEGUtils extends Const
|
|||
System.err.println("JPEGUtils extract found start for \""+new String(key) +"\": false");
|
||||
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)
|
||||
{
|
||||
byte[] meta = getXMPBlocksContent(data);
|
||||
|
@ -104,6 +142,12 @@ public class JPEGUtils extends Const
|
|||
if(depth == null) System.err.println("JPEGUtils extractDepthMap is null");
|
||||
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)
|
||||
{
|
||||
byte[] meta = getXMPBlocksContent(data);
|
||||
|
@ -111,6 +155,12 @@ public class JPEGUtils extends Const
|
|||
if(src == null) System.err.println("JPEGUtils extractSourceImage is null");
|
||||
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)
|
||||
{
|
||||
byte[] o = new byte[2];
|
||||
|
@ -118,6 +168,13 @@ public class JPEGUtils extends Const
|
|||
o[1] = (byte) (l%256);
|
||||
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)
|
||||
{
|
||||
if(!JPEGUtils.isAPP1Marker(data,boundary))
|
||||
|
@ -127,9 +184,16 @@ public class JPEGUtils extends Const
|
|||
}
|
||||
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)
|
||||
{
|
||||
if(!JPEGUtils.isAPP1Marker(data,boundary))
|
||||
|
@ -137,9 +201,16 @@ public class JPEGUtils extends Const
|
|||
System.err.println("JPEGUtils getBlockWithoutAPP1: Block is no APP1-block");
|
||||
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)
|
||||
{
|
||||
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, markEXIF, 4+boundary)) offset = 2 + 2 + markEXIF.length;
|
||||
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[]
|
||||
* These are the bytes marking h´the beginning of a block
|
||||
* @param data byte array
|
||||
* @return array of positions
|
||||
* @return array of positions of APP1 marker
|
||||
*/
|
||||
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
|
||||
* @param data
|
||||
* 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
|
||||
* @return exif block
|
||||
*/
|
||||
public static byte[] getEXIFBlock(byte[] data)
|
||||
|
@ -195,6 +267,12 @@ public class JPEGUtils extends Const
|
|||
System.err.println("JPEGUtils getEXIFBlock: No EXIF-block found");
|
||||
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)
|
||||
{
|
||||
int[] bounds = JPEGUtils.getBoundaries(data);
|
||||
|
@ -212,6 +290,11 @@ public class JPEGUtils extends Const
|
|||
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)
|
||||
{
|
||||
byte[] out;
|
||||
|
@ -222,6 +305,12 @@ public class JPEGUtils extends Const
|
|||
out = Arrays.copyOfRange(data, offset, data.length);
|
||||
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)
|
||||
{
|
||||
int[] boundaries = getBoundaries(data);
|
||||
|
@ -232,6 +321,12 @@ public class JPEGUtils extends Const
|
|||
}
|
||||
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)
|
||||
{
|
||||
int[] bounds = JPEGUtils.getBoundaries(data);
|
||||
|
@ -243,6 +338,12 @@ public class JPEGUtils extends Const
|
|||
System.err.println("JPEGUtils getStandardXMPBlockContent is 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)
|
||||
{
|
||||
byte[] stand = getStandardXMPBlockContent(data);
|
||||
|
@ -252,48 +353,96 @@ public class JPEGUtils extends Const
|
|||
if(out==null) System.err.println("JPEGUtils getXMPBlocksContent is null");
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
return ArrayUtils.arrayIsPartOfOtherArrayOnOffset(data, markEXIF, offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Block is the block with FFE1 and the two following bytes
|
||||
* @param block block
|
||||
* @return true, if the block is extended xmp
|
||||
* Returns true, if block is an extendedXMPBlock.
|
||||
* @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
|
||||
*/
|
||||
public static boolean isExtendedXMP(byte[] block)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
return ArrayUtils.arrayIsPartOfOtherArrayOnOffset(data, markJPG, offset);
|
||||
}
|
||||
/**
|
||||
* Block is the block with FFE1 and the two following bytes
|
||||
* @param block block
|
||||
* @return true, if the block is standard xmp
|
||||
* Returns true, if data is a standardXMPBlock
|
||||
* @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
|
||||
*/
|
||||
public static boolean isStandardXMP(byte[] block)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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: key not found in data");
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue