334 lines
9.6 KiB
Java
334 lines
9.6 KiB
Java
|
|
||
|
|
||
|
import java.util.Arrays;
|
||
|
import java.util.Vector;
|
||
|
|
||
|
public class JPEGUtils extends Const
|
||
|
{
|
||
|
|
||
|
public static int blockLength(int boundaryPos, byte[] data)
|
||
|
{
|
||
|
if(!JPEGUtils.isAPP1Marker(data,boundaryPos))
|
||
|
{
|
||
|
System.err.println("JPEGUtils blockLength: Block is no APP1 Block!");
|
||
|
return -1;
|
||
|
}
|
||
|
int o;
|
||
|
try
|
||
|
{
|
||
|
o = 256*(data[boundaryPos+2]&0xFF) + (data[boundaryPos+3]&0xFF);
|
||
|
}
|
||
|
catch(ArrayIndexOutOfBoundsException e)
|
||
|
{
|
||
|
System.err.println("JPEGUtils blockLength got ArrayIndexOutOfBoundsException:");
|
||
|
e.printStackTrace();
|
||
|
return -1;
|
||
|
}
|
||
|
return o;
|
||
|
}
|
||
|
public static byte[] decorateBlock(byte[] data, String type)
|
||
|
{
|
||
|
if(type.equals(EXIF))
|
||
|
{
|
||
|
data = ArrayUtils.concatenate(markEXIF, data);
|
||
|
byte[] pre = ArrayUtils.concatenate(markAPP1, genLen(data.length+2));
|
||
|
return ArrayUtils.concatenate(pre, 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);
|
||
|
}
|
||
|
else if(type.equals(EXTENDEDXMP))
|
||
|
{
|
||
|
byte[] out = new byte[0];
|
||
|
byte[] md5 = HexUtil.generateMD5(data);
|
||
|
int i=0;
|
||
|
int blockCount = data.length/CHUNKSIZE;
|
||
|
while (i<blockCount)
|
||
|
{
|
||
|
byte[] part = Arrays.copyOfRange(data, i*CHUNKSIZE, (i+1)*CHUNKSIZE);
|
||
|
byte[] pre = markAPP1;
|
||
|
pre = ArrayUtils.concatenate(pre, genLen(2+markExtendedXMP.length + 32 + 4 + 4 + part.length));
|
||
|
pre = ArrayUtils.concatenate(pre, markExtendedXMP);
|
||
|
pre = ArrayUtils.concatenate(pre, md5);
|
||
|
pre = ArrayUtils.concatenate(pre, ArrayUtils.intToByteArray(data.length));
|
||
|
pre = ArrayUtils.concatenate(pre, ArrayUtils.intToByteArray(i*CHUNKSIZE));
|
||
|
part = ArrayUtils.concatenate(pre, part);
|
||
|
out = ArrayUtils.concatenate(out, part);
|
||
|
i++;
|
||
|
}
|
||
|
//Rest
|
||
|
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));
|
||
|
pre = ArrayUtils.concatenate(pre, markExtendedXMP);
|
||
|
pre = ArrayUtils.concatenate(pre, md5);
|
||
|
pre = ArrayUtils.concatenate(pre, ArrayUtils.intToByteArray(data.length));
|
||
|
pre = ArrayUtils.concatenate(pre, ArrayUtils.intToByteArray(i*CHUNKSIZE));
|
||
|
part = ArrayUtils.concatenate(pre, part);
|
||
|
out = ArrayUtils.concatenate(out, part);
|
||
|
return out;
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
public static byte[] extract(byte[] data, byte[] key)
|
||
|
{
|
||
|
int start = -1;
|
||
|
int end = -1;
|
||
|
for(int i=0; i<=data.length-key.length; i++)
|
||
|
{
|
||
|
if(ArrayUtils.arrayIsPartOfOtherArrayOnOffset(data, key, i) && data[i+key.length] == 0x3D && data[i+key.length+1] == 0x22)
|
||
|
{
|
||
|
start = i+key.length+2;
|
||
|
for(int j=i+key.length+2; j<data.length; j++)
|
||
|
{
|
||
|
if(data[j] == 0x22)
|
||
|
{
|
||
|
end = j;
|
||
|
return Arrays.copyOfRange(data, start, end);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
System.err.println("JPEGUtils extract found end for \""+new String(key) +"\": false");
|
||
|
}
|
||
|
}
|
||
|
System.err.println("JPEGUtils extract found start for \""+new String(key) +"\": false");
|
||
|
return null;
|
||
|
}
|
||
|
public static byte[] extractDepthMap(byte[] data)
|
||
|
{
|
||
|
byte[] meta = getXMPBlocksContent(data);
|
||
|
byte[] depth = extract(meta, keyGDepthData);
|
||
|
if(depth == null) System.err.println("JPEGUtils extractDepthMap is null");
|
||
|
return depth;
|
||
|
}
|
||
|
public static byte[] extractSourceImage(byte[] data)
|
||
|
{
|
||
|
byte[] meta = getXMPBlocksContent(data);
|
||
|
byte[] src = extract(meta, keyGImageData);
|
||
|
if(src == null) System.err.println("JPEGUtils extractSourceImage is null");
|
||
|
return src;
|
||
|
}
|
||
|
public static byte[] genLen(int l)
|
||
|
{
|
||
|
byte[] o = new byte[2];
|
||
|
o[0] = (byte) (l/256);
|
||
|
o[1] = (byte) (l%256);
|
||
|
return o;
|
||
|
}
|
||
|
public static byte[] getBlock(byte[] data, int boundary)
|
||
|
{
|
||
|
if(!JPEGUtils.isAPP1Marker(data,boundary))
|
||
|
{
|
||
|
System.err.println("JPEGUtils getBlock: Block is no APP1-block");
|
||
|
return data;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return Arrays.copyOfRange(data, boundary, boundary+JPEGUtils.blockLength(boundary, data)+2);
|
||
|
}
|
||
|
}
|
||
|
public static byte[] getBlockWithoutAPP1(byte[] data, int boundary)
|
||
|
{
|
||
|
if(!JPEGUtils.isAPP1Marker(data,boundary))
|
||
|
{
|
||
|
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);
|
||
|
|
||
|
}
|
||
|
public static byte[] getBlockWithoutHeader(byte[] data, int boundary)
|
||
|
{
|
||
|
if(!JPEGUtils.isAPP1Marker(data,boundary))
|
||
|
{
|
||
|
System.err.println("JPEGUtils getBlockWithoutHeader: Block is no APP1-block");
|
||
|
return null;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
int offset;
|
||
|
if(ArrayUtils.arrayIsPartOfOtherArrayOnOffset(data, markStandardXMP, 4+boundary)) offset = 2 + 2 + markStandardXMP.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;
|
||
|
return Arrays.copyOfRange(data, boundary+offset, boundary+JPEGUtils.blockLength(boundary,data)+2);
|
||
|
}
|
||
|
}
|
||
|
/**
|
||
|
* Return all positions of "FF E1" in the byte[]
|
||
|
* @param data byte array
|
||
|
* @return array of positions
|
||
|
*/
|
||
|
public static int[] getBoundaries(byte[] data)
|
||
|
{
|
||
|
Vector<Integer> b = new Vector<Integer>();
|
||
|
for(int i=0; i<data.length; i++)
|
||
|
{
|
||
|
if(JPEGUtils.isAPP1Marker(data,i))
|
||
|
{
|
||
|
b.add(i);
|
||
|
i+=3; //Skip E1 and length
|
||
|
}
|
||
|
}
|
||
|
int[] out = new int[b.size()];
|
||
|
for(int i=0; i<b.size(); i++)
|
||
|
out[i] = b.get(i);
|
||
|
return out;
|
||
|
|
||
|
}
|
||
|
/**
|
||
|
* Return the exif block of the jpg. This is usually the first block of data
|
||
|
* @param data
|
||
|
* @return exif block
|
||
|
*/
|
||
|
public static byte[] getEXIFBlock(byte[] data)
|
||
|
{
|
||
|
int[] bounds = getBoundaries(data);
|
||
|
for(int e : bounds)
|
||
|
{
|
||
|
byte[] block = getBlock(data, e);
|
||
|
if(isEXIFBlock(block)) return getBlockWithoutHeader(data,e);
|
||
|
}
|
||
|
System.err.println("JPEGUtils getEXIFBlock: No EXIF-block found");
|
||
|
return null;
|
||
|
}
|
||
|
public static byte[] getExtendedXMPBlockContent(byte[] data)
|
||
|
{
|
||
|
int[] bounds = JPEGUtils.getBoundaries(data);
|
||
|
byte[] cont = new byte[0];
|
||
|
for(int e : bounds)
|
||
|
{
|
||
|
byte[] block = JPEGUtils.getBlock(data,e);
|
||
|
if(JPEGUtils.isExtendedXMP(block))
|
||
|
{
|
||
|
byte[] part = JPEGUtils.getBlockWithoutHeader(block,0);
|
||
|
if(part == null) System.err.println("JPEGUtils getExtendedXMPBlockContent part is null");
|
||
|
cont = ArrayUtils.concatenate(cont, part);
|
||
|
}
|
||
|
}
|
||
|
return cont;
|
||
|
}
|
||
|
|
||
|
public static byte[] getImageTail(byte[] data)
|
||
|
{
|
||
|
byte[] out;
|
||
|
int[] bounds = getBoundaries(data);
|
||
|
int offset = 256*(data[bounds[bounds.length-1]+2]&0xFF) + (data[bounds[bounds.length-1]+3]&0xFF);
|
||
|
offset += bounds[bounds.length-1];
|
||
|
offset +=2;
|
||
|
out = Arrays.copyOfRange(data, offset, data.length);
|
||
|
return out;
|
||
|
}
|
||
|
public static byte[] getMetadata(byte[] data)
|
||
|
{
|
||
|
int[] boundaries = getBoundaries(data);
|
||
|
byte[] out = new byte[0];
|
||
|
for(int e : boundaries)
|
||
|
{
|
||
|
out = ArrayUtils.concatenate(out, getBlockWithoutAPP1(data,e));
|
||
|
}
|
||
|
return out;
|
||
|
}
|
||
|
public static byte[] getStandardXMPBlockContent(byte[] data)
|
||
|
{
|
||
|
int[] bounds = JPEGUtils.getBoundaries(data);
|
||
|
for(int e : bounds)
|
||
|
{
|
||
|
byte[] block = JPEGUtils.getBlock(data, e);
|
||
|
if(JPEGUtils.isStandardXMP(block)) return JPEGUtils.getBlockWithoutHeader(data,e);
|
||
|
}
|
||
|
System.err.println("JPEGUtils getStandardXMPBlockContent is null");
|
||
|
return null;
|
||
|
}
|
||
|
public static byte[] getXMPBlocksContent(byte[] data)
|
||
|
{
|
||
|
byte[] stand = getStandardXMPBlockContent(data);
|
||
|
byte[] ext = getExtendedXMPBlockContent(data);
|
||
|
|
||
|
byte[] out = ArrayUtils.concatenate(stand, ext);
|
||
|
if(out==null) System.err.println("JPEGUtils getXMPBlocksContent is null");
|
||
|
return out;
|
||
|
}
|
||
|
public static boolean isAPP1Marker(byte[] data, int offset)
|
||
|
{
|
||
|
return ArrayUtils.arrayIsPartOfOtherArrayOnOffset(data, markAPP1, offset);
|
||
|
}
|
||
|
public static boolean isEXIFBlock(byte[] block)
|
||
|
{
|
||
|
return JPEGUtils.isEXIFMarker(block, 4);
|
||
|
}
|
||
|
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
|
||
|
*/
|
||
|
public static boolean isExtendedXMP(byte[] block)
|
||
|
{
|
||
|
return ArrayUtils.arrayIsPartOfOtherArrayOnOffset(block, markExtendedXMP, 4);
|
||
|
}
|
||
|
public static boolean isJPG(byte[] data)
|
||
|
{
|
||
|
return JPEGUtils.isJPG(data, 0);
|
||
|
}
|
||
|
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
|
||
|
*/
|
||
|
public static boolean isStandardXMP(byte[] block)
|
||
|
{
|
||
|
return ArrayUtils.arrayIsPartOfOtherArrayOnOffset(block, markStandardXMP, 4);
|
||
|
}
|
||
|
public static boolean isStandardXMP(byte[] block, int offset)
|
||
|
{
|
||
|
return ArrayUtils.arrayIsPartOfOtherArrayOnOffset(block, markStandardXMP, offset);
|
||
|
}
|
||
|
public static byte[] replace(byte[] data, byte[] key, byte[] value)
|
||
|
{
|
||
|
int start = -1;
|
||
|
int end = -1;
|
||
|
for(int i=0; i<data.length; i++)
|
||
|
{
|
||
|
if(ArrayUtils.arrayIsPartOfOtherArrayOnOffset(data, key, i))
|
||
|
{
|
||
|
start = i+key.length+2;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if(start != -1)
|
||
|
{
|
||
|
byte[] pre = Arrays.copyOfRange(data, 0, start);
|
||
|
for(int j=start; j<data.length; j++)
|
||
|
{
|
||
|
if(data[j] == 0x22)
|
||
|
{
|
||
|
end = j;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if(end != -1)
|
||
|
{
|
||
|
byte[] post = Arrays.copyOfRange(data, end, data.length);
|
||
|
byte[] out = ArrayUtils.concatenate(pre, value);
|
||
|
out = ArrayUtils.concatenate(out,post);
|
||
|
return out;
|
||
|
}
|
||
|
else System.err.println("JPEGUtils replace: No closing \" found in data");
|
||
|
}
|
||
|
else System.err.println("JPEGUtils replace: key not found in data");
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
}
|