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