Added Base64Wrapper to support different Base64 Implementaions (like android). Also added some functionality for android compatibility
This commit is contained in:
parent
d7ccf6883c
commit
7cf965b1f5
9 changed files with 480 additions and 309 deletions
Binary file not shown.
|
@ -1,59 +1,63 @@
|
||||||
|
|
||||||
|
|
||||||
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 part array
|
* @param src array
|
||||||
|
* @param part array
|
||||||
* @param offset int
|
* @param offset int
|
||||||
* @return true, if part is completely a subarray of src on offset offset, else false
|
* @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;
|
||||||
for(int i=0; i<part.length; i++)
|
for (int i = 0; i < part.length; i++)
|
||||||
{
|
{
|
||||||
if(part[i] != src[i+offset]) return false;
|
if (part[i] != src[i+offset]) return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
*/
|
*/
|
||||||
public static char[] bytesToChars(byte[] b)
|
public static char[] bytesToChars(byte[] b)
|
||||||
{
|
{
|
||||||
char[] c = new char[b.length];
|
char[] c = new char[b.length];
|
||||||
for(int i=0; i<b.length; i++)
|
for (int i = 0; i < b.length; i++)
|
||||||
c[i] = (char) b[i];
|
c[i] = (char) b[i];
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
*/
|
*/
|
||||||
public static byte[] charsToBytes(char[] c)
|
public static byte[] charsToBytes(char[] c)
|
||||||
{
|
{
|
||||||
byte[] b = new byte[c.length];
|
byte[] b = new byte[c.length];
|
||||||
for(int i=0; i<c.length; i++)
|
for (int i = 0; i < c.length; i++)
|
||||||
b[i] = (byte) c[i];
|
b[i] = (byte) c[i];
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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(second == null) return first;
|
if (first == null) return second;
|
||||||
|
if (second == null) return first;
|
||||||
int aLen = first.length;
|
int aLen = first.length;
|
||||||
int bLen = second.length;
|
int bLen = second.length;
|
||||||
byte[] c = new byte[aLen+bLen];
|
byte[] c = new byte[aLen+bLen];
|
||||||
|
@ -64,27 +68,30 @@ 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[] {
|
{
|
||||||
(byte)(value >>> 24),
|
return new byte[]{
|
||||||
(byte)(value >>> 16),
|
(byte) (value >>> 24),
|
||||||
(byte)(value >>> 8),
|
(byte) (value >>> 16),
|
||||||
(byte)value};
|
(byte) (value >>> 8),
|
||||||
|
(byte) value};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
*/
|
*/
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
9
src/Base64Wrapper.java
Normal file
9
src/Base64Wrapper.java
Normal 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);
|
||||||
|
}
|
|
@ -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)
|
||||||
*/
|
*/
|
||||||
|
@ -32,25 +38,25 @@ public class Const
|
||||||
* Header of the StandardXMP block (http://ns.adobe.com/xap/1.0/\0) (length: 29)
|
* Header of the StandardXMP block (http://ns.adobe.com/xap/1.0/\0) (length: 29)
|
||||||
*/
|
*/
|
||||||
public static byte[] markStandardXMP = {
|
public static byte[] markStandardXMP = {
|
||||||
(byte) 0x68, (byte) 0x74, (byte) 0x74, (byte) 0x70,
|
(byte) 0x68, (byte) 0x74, (byte) 0x74, (byte) 0x70,
|
||||||
(byte) 0x3A, (byte) 0x2F, (byte) 0x2F, (byte) 0x6E, (byte) 0x73,
|
(byte) 0x3A, (byte) 0x2F, (byte) 0x2F, (byte) 0x6E, (byte) 0x73,
|
||||||
(byte) 0x2E, (byte) 0x61, (byte) 0x64, (byte) 0x6F, (byte) 0x62,
|
(byte) 0x2E, (byte) 0x61, (byte) 0x64, (byte) 0x6F, (byte) 0x62,
|
||||||
(byte) 0x65, (byte) 0x2E, (byte) 0x63, (byte) 0x6F, (byte) 0x6D,
|
(byte) 0x65, (byte) 0x2E, (byte) 0x63, (byte) 0x6F, (byte) 0x6D,
|
||||||
(byte) 0x2F, (byte) 0x78, (byte) 0x61, (byte) 0x70, (byte) 0x2F,
|
(byte) 0x2F, (byte) 0x78, (byte) 0x61, (byte) 0x70, (byte) 0x2F,
|
||||||
(byte) 0x31, (byte) 0x2E, (byte) 0x30, (byte) 0x2F, (byte) 0x00};
|
(byte) 0x31, (byte) 0x2E, (byte) 0x30, (byte) 0x2F, (byte) 0x00};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Header of the ExtendedXMP block (http://ns.adobe.com/xmp/extension/\0) (length: 35)
|
* Header of the ExtendedXMP block (http://ns.adobe.com/xmp/extension/\0) (length: 35)
|
||||||
*/
|
*/
|
||||||
public static byte[] markExtendedXMP = {
|
public static byte[] markExtendedXMP = {
|
||||||
(byte) 0x68, (byte) 0x74, (byte) 0x74, (byte) 0x70,
|
(byte) 0x68, (byte) 0x74, (byte) 0x74, (byte) 0x70,
|
||||||
(byte) 0x3A, (byte) 0x2F, (byte) 0x2F, (byte) 0x6E, (byte) 0x73,
|
(byte) 0x3A, (byte) 0x2F, (byte) 0x2F, (byte) 0x6E, (byte) 0x73,
|
||||||
(byte) 0x2E, (byte) 0x61, (byte) 0x64, (byte) 0x6F, (byte) 0x62,
|
(byte) 0x2E, (byte) 0x61, (byte) 0x64, (byte) 0x6F, (byte) 0x62,
|
||||||
(byte) 0x65, (byte) 0x2E, (byte) 0x63, (byte) 0x6F, (byte) 0x6D,
|
(byte) 0x65, (byte) 0x2E, (byte) 0x63, (byte) 0x6F, (byte) 0x6D,
|
||||||
(byte) 0x2F, (byte) 0x78, (byte) 0x6D, (byte) 0x70, (byte) 0x2F,
|
(byte) 0x2F, (byte) 0x78, (byte) 0x6D, (byte) 0x70, (byte) 0x2F,
|
||||||
(byte) 0x65, (byte) 0x78, (byte) 0x74, (byte) 0x65, (byte) 0x6e,
|
(byte) 0x65, (byte) 0x78, (byte) 0x74, (byte) 0x65, (byte) 0x6e,
|
||||||
(byte) 0x73, (byte) 0x69, (byte) 0x6f, (byte) 0x6e, (byte) 0x2f,
|
(byte) 0x73, (byte) 0x69, (byte) 0x6f, (byte) 0x6e, (byte) 0x2f,
|
||||||
(byte) 0x00};
|
(byte) 0x00};
|
||||||
|
|
||||||
/** Keys (following scheme: key="value") */
|
/** Keys (following scheme: key="value") */
|
||||||
|
|
||||||
|
@ -59,11 +65,11 @@ public class Const
|
||||||
* (xmpNote:HasExtendedXMP) (length: 22)
|
* (xmpNote:HasExtendedXMP) (length: 22)
|
||||||
*/
|
*/
|
||||||
public static byte[] keyHasExtendedXMP = {
|
public static byte[] keyHasExtendedXMP = {
|
||||||
(byte) 0x78,(byte) 0x6d,(byte) 0x70,(byte) 0x4e,(byte) 0x6f,
|
(byte) 0x78, (byte) 0x6d, (byte) 0x70, (byte) 0x4e, (byte) 0x6f,
|
||||||
(byte) 0x74,(byte) 0x65,(byte) 0x3a,(byte) 0x48,(byte) 0x61,
|
(byte) 0x74, (byte) 0x65, (byte) 0x3a, (byte) 0x48, (byte) 0x61,
|
||||||
(byte) 0x73,(byte) 0x45,(byte) 0x78,(byte) 0x74,(byte) 0x65,
|
(byte) 0x73, (byte) 0x45, (byte) 0x78, (byte) 0x74, (byte) 0x65,
|
||||||
(byte) 0x6e,(byte) 0x64,(byte) 0x65,(byte) 0x64,(byte) 0x58,
|
(byte) 0x6e, (byte) 0x64, (byte) 0x65, (byte) 0x64, (byte) 0x58,
|
||||||
(byte) 0x4d,(byte) 0x50
|
(byte) 0x4d, (byte) 0x50
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -71,18 +77,18 @@ public class Const
|
||||||
* (GDepth:Data) (length: 11)
|
* (GDepth:Data) (length: 11)
|
||||||
*/
|
*/
|
||||||
public static byte[] keyGDepthData = {
|
public static byte[] keyGDepthData = {
|
||||||
(byte) 0x47,(byte) 0x44,(byte) 0x65,(byte) 0x70,(byte) 0x74,
|
(byte) 0x47, (byte) 0x44, (byte) 0x65, (byte) 0x70, (byte) 0x74,
|
||||||
(byte) 0x68,(byte) 0x3a,(byte) 0x44,(byte) 0x61,(byte) 0x74,
|
(byte) 0x68, (byte) 0x3a, (byte) 0x44, (byte) 0x61, (byte) 0x74,
|
||||||
(byte) 0x61
|
(byte) 0x61
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Key for the Base64 encoded unblurred jpg source image (Google camera will combine this with the depth information to render the blurred image)
|
* 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)
|
* (GImage:Data) (length: 11)
|
||||||
*/
|
*/
|
||||||
public static byte[] keyGImageData = {
|
public static byte[] keyGImageData = {
|
||||||
(byte) 0x47,(byte) 0x49,(byte) 0x6d,(byte) 0x61,(byte) 0x67,
|
(byte) 0x47, (byte) 0x49, (byte) 0x6d, (byte) 0x61, (byte) 0x67,
|
||||||
(byte) 0x65,(byte) 0x3a,(byte) 0x44,(byte) 0x61,(byte) 0x74,
|
(byte) 0x65, (byte) 0x3a, (byte) 0x44, (byte) 0x61, (byte) 0x74,
|
||||||
(byte) 0x61
|
(byte) 0x61
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,47 +1,81 @@
|
||||||
|
|
||||||
|
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]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//export source image
|
//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++)
|
||||||
{
|
{
|
||||||
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,16 +87,19 @@ public class DepthMapNeedle
|
||||||
public static void help()
|
public static void help()
|
||||||
{
|
{
|
||||||
System.out.println("Welcome to DepthMapNeedle!"
|
System.out.println("Welcome to 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'-h':"
|
+"\n'-S <unblurred>.jpg <file1>.jpg ... <fileN>.jpg':"
|
||||||
+ "\n Show this help text.");
|
+"\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 Show this help text.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
|
|
||||||
|
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
@ -7,19 +6,22 @@ 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)
|
||||||
{
|
{
|
||||||
for(byte b : bytes) System.out.print(byteToHexString(b));
|
for (byte b : bytes) System.out.print(byteToHexString(b));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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)) {
|
{
|
||||||
return (char) ('0' + i);
|
if ((0 <= i) && (i <= 9))
|
||||||
} else {
|
{
|
||||||
return (char) ('a' + (i - 10));
|
return (char) ('0'+i);
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
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
|
||||||
*/
|
*/
|
||||||
|
@ -51,10 +58,10 @@ public class HexUtil
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
byte[] md5 = MessageDigest.getInstance("MD5").digest(data);
|
byte[] md5 = MessageDigest.getInstance("MD5").digest(data);
|
||||||
String m="";
|
String m = "";
|
||||||
for(int i=0; i<md5.length; i++)
|
for (int i = 0; i < md5.length; i++)
|
||||||
{
|
{
|
||||||
m = m + byteToHexString(md5[i]).toUpperCase();
|
m = m+byteToHexString(md5[i]).toUpperCase();
|
||||||
}
|
}
|
||||||
return m.getBytes();
|
return m.getBytes();
|
||||||
} catch (NoSuchAlgorithmException e)
|
} catch (NoSuchAlgorithmException e)
|
||||||
|
|
13
src/IO.java
13
src/IO.java
|
@ -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,7 +32,8 @@ 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
|
||||||
*/
|
*/
|
||||||
public static void write(byte[] b, String filepath)
|
public static void write(byte[] b, String filepath)
|
||||||
|
@ -40,7 +41,7 @@ public class IO
|
||||||
FileOutputStream fos;
|
FileOutputStream fos;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
fos=new FileOutputStream(filepath);
|
fos = new FileOutputStream(filepath);
|
||||||
fos.write(b);
|
fos.write(b);
|
||||||
fos.close();
|
fos.close();
|
||||||
} catch (Exception e)
|
} catch (Exception e)
|
||||||
|
|
207
src/JPEG.java
207
src/JPEG.java
|
@ -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.init(rawData, null);
|
this.base64 = wrapper;
|
||||||
|
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 depthmap 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.disassemble();
|
this.m_RawData = raw;
|
||||||
|
this.m_Filename = filename;
|
||||||
|
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)
|
||||||
|
@ -323,38 +394,42 @@ public class JPEG extends Const
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the exif memberto 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
|
|
||||||
|
@ -7,14 +6,15 @@ 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
|
||||||
*/
|
*/
|
||||||
public static int readBlockLength(int boundaryPos, byte[] data)
|
public static int readBlockLength(int boundaryPos, byte[] data)
|
||||||
{
|
{
|
||||||
//Check whether entered position is APP1 Marker
|
//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;
|
||||||
|
@ -23,9 +23,8 @@ public class JPEGUtils extends Const
|
||||||
//Read length
|
//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 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
|
||||||
|
@ -46,49 +46,49 @@ public class JPEGUtils extends Const
|
||||||
public static byte[] decorateBlock(byte[] data, String type)
|
public static byte[] decorateBlock(byte[] data, String type)
|
||||||
{
|
{
|
||||||
//EXIF Block: 'APP1 + BLOCKLENGTH + EXIF\0\0 + data'
|
//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'
|
//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
|
//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
|
//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
|
//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);
|
||||||
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));
|
||||||
pre = ArrayUtils.concatenate(pre, markExtendedXMP);
|
pre = ArrayUtils.concatenate(pre, markExtendedXMP);
|
||||||
pre = ArrayUtils.concatenate(pre, md5);
|
pre = ArrayUtils.concatenate(pre, md5);
|
||||||
pre = ArrayUtils.concatenate(pre, ArrayUtils.intToByteArray(data.length));
|
pre = ArrayUtils.concatenate(pre, ArrayUtils.intToByteArray(data.length));
|
||||||
pre = ArrayUtils.concatenate(pre, ArrayUtils.intToByteArray(i*CHUNKSIZE));
|
pre = ArrayUtils.concatenate(pre, ArrayUtils.intToByteArray(i * CHUNKSIZE));
|
||||||
part = ArrayUtils.concatenate(pre, part);
|
part = ArrayUtils.concatenate(pre, part);
|
||||||
out = ArrayUtils.concatenate(out, part);
|
out = ArrayUtils.concatenate(out, part);
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
//Decorate the restportion < CHUNKSIZE
|
//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));
|
||||||
pre = ArrayUtils.concatenate(pre, markExtendedXMP);
|
pre = ArrayUtils.concatenate(pre, markExtendedXMP);
|
||||||
pre = ArrayUtils.concatenate(pre, md5);
|
pre = ArrayUtils.concatenate(pre, md5);
|
||||||
pre = ArrayUtils.concatenate(pre, ArrayUtils.intToByteArray(data.length));
|
pre = ArrayUtils.concatenate(pre, ArrayUtils.intToByteArray(data.length));
|
||||||
pre = ArrayUtils.concatenate(pre, ArrayUtils.intToByteArray(i*CHUNKSIZE));
|
pre = ArrayUtils.concatenate(pre, ArrayUtils.intToByteArray(i * CHUNKSIZE));
|
||||||
part = ArrayUtils.concatenate(pre, part);
|
part = ArrayUtils.concatenate(pre, part);
|
||||||
out = ArrayUtils.concatenate(out, part);
|
out = ArrayUtils.concatenate(out, part);
|
||||||
return out;
|
return out;
|
||||||
|
@ -101,37 +101,39 @@ 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 '"'.
|
||||||
*/
|
*/
|
||||||
public static byte[] extract(byte[] data, byte[] key)
|
public static byte[] extract(byte[] data, byte[] key)
|
||||||
{
|
{
|
||||||
int start = -1;
|
int start = -1;
|
||||||
int end = -1;
|
int end = -1;
|
||||||
for(int i=0; i<=data.length-key.length; i++)
|
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)
|
if (ArrayUtils.arrayIsPartOfOtherArrayOnOffset(data, key, i) && data[i+key.length] == 0x3D && data[i+key.length+1] == 0x22)
|
||||||
{
|
{
|
||||||
start = i+key.length+2;
|
start = i+key.length+2;
|
||||||
for(int j=i+key.length+2; j<data.length; j++)
|
for (int j = i+key.length+2; j < data.length; j++)
|
||||||
{
|
{
|
||||||
if(data[j] == 0x22)
|
if (data[j] == 0x22)
|
||||||
{
|
{
|
||||||
end = j;
|
end = j;
|
||||||
return Arrays.copyOfRange(data, start, end);
|
return Arrays.copyOfRange(data, start, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
System.err.println("JPEGUtils extract found end for \""+new String(key) +"\": false");
|
System.err.println("JPEGUtils extract found end for \""+new String(key)+"\": false");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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)
|
* 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)
|
||||||
*/
|
*/
|
||||||
|
@ -139,12 +141,13 @@ public class JPEGUtils extends Const
|
||||||
{
|
{
|
||||||
byte[] meta = getXMPBlocksContent(data);
|
byte[] meta = getXMPBlocksContent(data);
|
||||||
byte[] depth = extract(meta, keyGDepthData);
|
byte[] depth = extract(meta, keyGDepthData);
|
||||||
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)
|
* 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)
|
||||||
*/
|
*/
|
||||||
|
@ -152,37 +155,38 @@ public class JPEGUtils extends Const
|
||||||
{
|
{
|
||||||
byte[] meta = getXMPBlocksContent(data);
|
byte[] meta = getXMPBlocksContent(data);
|
||||||
byte[] src = extract(meta, keyGImageData);
|
byte[] src = extract(meta, keyGImageData);
|
||||||
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
|
* 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
|
||||||
*/
|
*/
|
||||||
public static byte[] genLen(int l)
|
public static byte[] genLen(int l)
|
||||||
{
|
{
|
||||||
byte[] o = new byte[2];
|
byte[] o = new byte[2];
|
||||||
o[0] = (byte) (l/256);
|
o[0] = (byte) (l / 256);
|
||||||
o[1] = (byte) (l%256);
|
o[1] = (byte) (l % 256);
|
||||||
return o;
|
return o;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
*/
|
*/
|
||||||
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))
|
||||||
{
|
{
|
||||||
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,79 +194,87 @@ 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
|
||||||
*/
|
*/
|
||||||
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))
|
||||||
{
|
{
|
||||||
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
|
||||||
*/
|
*/
|
||||||
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))
|
||||||
{
|
{
|
||||||
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
|
||||||
*/
|
*/
|
||||||
public static int[] getBoundaries(byte[] data)
|
public static int[] getBoundaries(byte[] data)
|
||||||
{
|
{
|
||||||
Vector<Integer> b = new Vector<Integer>();
|
Vector<Integer> b = new Vector<Integer>();
|
||||||
for(int i=0; i<data.length; i++)
|
for (int i = 0; i < data.length; i++)
|
||||||
{
|
{
|
||||||
if(JPEGUtils.isAPP1Marker(data,i))
|
if (JPEGUtils.isAPP1Marker(data, i))
|
||||||
{
|
{
|
||||||
b.add(i);
|
b.add(i);
|
||||||
i+=3; //Skip E1 and length
|
i += 3; //Skip E1 and length
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int[] out = new int[b.size()];
|
int[] out = new int[b.size()];
|
||||||
for(int i=0; i<b.size(); i++)
|
for (int i = 0; i < b.size(); i++)
|
||||||
out[i] = b.get(i);
|
out[i] = b.get(i);
|
||||||
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
|
||||||
*/
|
*/
|
||||||
public static byte[] getEXIFBlock(byte[] data)
|
public static byte[] getEXIFBlock(byte[] data)
|
||||||
{
|
{
|
||||||
int[] bounds = getBoundaries(data);
|
int[] bounds = getBoundaries(data);
|
||||||
for(int e : bounds)
|
for (int e : bounds)
|
||||||
{
|
{
|
||||||
byte[] block = getBlock(data, e);
|
byte[] block = getBlock(data, e);
|
||||||
if(isEXIFBlock(block)) return getBlockWithoutHeader(data,e);
|
if (isEXIFBlock(block)) return getBlockWithoutHeader(data, e);
|
||||||
}
|
}
|
||||||
System.err.println("JPEGUtils getEXIFBlock: No EXIF-block found");
|
System.err.println("JPEGUtils getEXIFBlock: No EXIF-block found");
|
||||||
return null;
|
return null;
|
||||||
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
@ -277,13 +290,14 @@ public class JPEGUtils extends Const
|
||||||
{
|
{
|
||||||
int[] bounds = JPEGUtils.getBoundaries(data);
|
int[] bounds = JPEGUtils.getBoundaries(data);
|
||||||
byte[] cont = new byte[0];
|
byte[] cont = new byte[0];
|
||||||
for(int e : bounds)
|
for (int e : bounds)
|
||||||
{
|
{
|
||||||
byte[] block = JPEGUtils.getBlock(data,e);
|
byte[] block = JPEGUtils.getBlock(data, e);
|
||||||
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);
|
||||||
int offset = 256*(data[bounds[bounds.length-1]+2]&0xFF) + (data[bounds[bounds.length-1]+3]&0xFF);
|
if (bounds.length != 0)
|
||||||
offset += bounds[bounds.length-1];
|
{
|
||||||
offset +=2;
|
int offset = 256 * (data[bounds[bounds.length-1]+2] & 0xFF)+(data[bounds[bounds.length-1]+3] & 0xFF);
|
||||||
out = Arrays.copyOfRange(data, offset, data.length);
|
offset += bounds[bounds.length-1];
|
||||||
return out;
|
offset += 2;
|
||||||
|
out = Arrays.copyOfRange(data, offset, data.length);
|
||||||
|
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
|
||||||
*/
|
*/
|
||||||
|
@ -315,25 +334,26 @@ public class JPEGUtils extends Const
|
||||||
{
|
{
|
||||||
int[] boundaries = getBoundaries(data);
|
int[] boundaries = getBoundaries(data);
|
||||||
byte[] out = new byte[0];
|
byte[] out = new byte[0];
|
||||||
for(int e : boundaries)
|
for (int e : boundaries)
|
||||||
{
|
{
|
||||||
out = ArrayUtils.concatenate(out, getBlockWithoutAPP1(data,e));
|
out = ArrayUtils.concatenate(out, getBlockWithoutAPP1(data, e));
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
*/
|
*/
|
||||||
public static byte[] getStandardXMPBlockContent(byte[] data)
|
public static byte[] getStandardXMPBlockContent(byte[] data)
|
||||||
{
|
{
|
||||||
int[] bounds = JPEGUtils.getBoundaries(data);
|
int[] bounds = JPEGUtils.getBoundaries(data);
|
||||||
for(int e : bounds)
|
for (int e : bounds)
|
||||||
{
|
{
|
||||||
byte[] block = JPEGUtils.getBlock(data, e);
|
byte[] block = JPEGUtils.getBlock(data, e);
|
||||||
if(JPEGUtils.isStandardXMP(block)) return JPEGUtils.getBlockWithoutHeader(data,e);
|
if (JPEGUtils.isStandardXMP(block)) return JPEGUtils.getBlockWithoutHeader(data, e);
|
||||||
}
|
}
|
||||||
System.err.println("JPEGUtils getStandardXMPBlockContent is null");
|
System.err.println("JPEGUtils getStandardXMPBlockContent is null");
|
||||||
return null;
|
return null;
|
||||||
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
@ -350,13 +371,14 @@ public class JPEGUtils extends Const
|
||||||
byte[] ext = getExtendedXMPBlockContent(data);
|
byte[] ext = getExtendedXMPBlockContent(data);
|
||||||
|
|
||||||
byte[] out = ArrayUtils.concatenate(stand, ext);
|
byte[] out = ArrayUtils.concatenate(stand, ext);
|
||||||
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.
|
* 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,7 +434,8 @@ 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,8 +468,9 @@ 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 key key of the value that will be modified
|
* @param data byte array
|
||||||
|
* @param key key of the value that will be modified
|
||||||
* @param value new value of key
|
* @param value new value of key
|
||||||
* @return modified data byte array
|
* @return modified data byte array
|
||||||
*/
|
*/
|
||||||
|
@ -447,35 +478,33 @@ public class JPEGUtils extends Const
|
||||||
{
|
{
|
||||||
int start = -1;
|
int start = -1;
|
||||||
int end = -1;
|
int end = -1;
|
||||||
for(int i=0; i<data.length; i++)
|
for (int i = 0; i < data.length; i++)
|
||||||
{
|
{
|
||||||
if(ArrayUtils.arrayIsPartOfOtherArrayOnOffset(data, key, i))
|
if (ArrayUtils.arrayIsPartOfOtherArrayOnOffset(data, key, i))
|
||||||
{
|
{
|
||||||
start = i+key.length+2;
|
start = i+key.length+2;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(start != -1)
|
if (start != -1)
|
||||||
{
|
{
|
||||||
byte[] pre = Arrays.copyOfRange(data, 0, start);
|
byte[] pre = Arrays.copyOfRange(data, 0, start);
|
||||||
for(int j=start; j<data.length; j++)
|
for (int j = start; j < data.length; j++)
|
||||||
{
|
{
|
||||||
if(data[j] == 0x22)
|
if (data[j] == 0x22)
|
||||||
{
|
{
|
||||||
end = j;
|
end = j;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(end != -1)
|
if (end != -1)
|
||||||
{
|
{
|
||||||
byte[] post = Arrays.copyOfRange(data, end, data.length);
|
byte[] post = Arrays.copyOfRange(data, end, data.length);
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue