From ba1535f39d5c28d5eeb7688249f6fdd4760b9518 Mon Sep 17 00:00:00 2001 From: gallonyin Date: Thu, 2 Mar 2023 01:19:56 +0800 Subject: [PATCH] =?UTF-8?q?update=20=E6=8E=A8=E9=80=81=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../worktool/observer/MultiFileObserver.java | 26 +- .../worktool/service/WeworkLoopImpl.kt | 34 +- .../yameida/worktool/service/WeworkService.kt | 3 + .../org/yameida/worktool/utils/ImageInfo.java | 1232 +++++++++++++++++ 4 files changed, 1271 insertions(+), 24 deletions(-) create mode 100644 app/src/main/java/org/yameida/worktool/utils/ImageInfo.java diff --git a/app/src/main/java/org/yameida/worktool/observer/MultiFileObserver.java b/app/src/main/java/org/yameida/worktool/observer/MultiFileObserver.java index 103cae1..0162aa9 100644 --- a/app/src/main/java/org/yameida/worktool/observer/MultiFileObserver.java +++ b/app/src/main/java/org/yameida/worktool/observer/MultiFileObserver.java @@ -3,6 +3,7 @@ package org.yameida.worktool.observer; import java.io.File; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Stack; @@ -12,8 +13,8 @@ import android.util.Log; public class MultiFileObserver extends FileObserver { public HashMap map = new HashMap<>(); - public static String lastPicPath = ""; - public static Long lastPicCreateTime = 0L; + public static HashSet createSet = new HashSet<>(); + public static HashSet finishSet = new HashSet<>(); /** Only modification events */ public static int CHANGES_ONLY = CREATE | MODIFY | DELETE | CLOSE_WRITE @@ -77,28 +78,35 @@ public class MultiFileObserver extends FileObserver { mObservers = null; } - @Override public void onEvent(int event, String path) { switch (event) { case FileObserver.ACCESS: + case FileObserver.CLOSE_WRITE: case FileObserver.CLOSE_NOWRITE: - if (path.endsWith(".0") && !map.containsKey(path)) { - Log.i("RecursiveFileObserver", "发现新图片: " + path); + if (path.endsWith(".0") && !map.containsKey(path) && createSet.contains(path)) { map.put(path, System.currentTimeMillis()); - lastPicPath = path; + Log.i("RecursiveFileObserver", "发现新图片: " + path); + finishSet.add(path); +// try { +// ImageInfo imageInfo = new ImageInfo(); +// imageInfo.setDetermineImageNumber(true); +// ImageInfo.run(path, new FileInputStream(path), imageInfo, true); +// } catch (Exception e) { e.printStackTrace(); } } break; case FileObserver.CREATE: Log.i("RecursiveFileObserver", "CREATE: " + path); - lastPicCreateTime = System.currentTimeMillis(); + if (path.endsWith(".0")) { + createSet.add(path); + } break; case FileObserver.ATTRIB: // Log.i("RecursiveFileObserver", "ATTRIB: " + path); break; - case FileObserver.CLOSE_WRITE: +// case FileObserver.CLOSE_WRITE: // Log.i("RecursiveFileObserver", "CLOSE_WRITE: " + path); - break; +// break; case FileObserver.DELETE: // Log.i("RecursiveFileObserver", "DELETE: " + path); break; diff --git a/app/src/main/java/org/yameida/worktool/service/WeworkLoopImpl.kt b/app/src/main/java/org/yameida/worktool/service/WeworkLoopImpl.kt index 72b15ab..eff681c 100644 --- a/app/src/main/java/org/yameida/worktool/service/WeworkLoopImpl.kt +++ b/app/src/main/java/org/yameida/worktool/service/WeworkLoopImpl.kt @@ -545,33 +545,37 @@ object WeworkLoopImpl { message = WeworkMessageBean.SubMessageBean(0, textType, itemMessageList, nameList) //图片类型特殊处理 if (doubleCheck && Constant.pushImage && textType == 2) { - val lastPicCreateTime = MultiFileObserver.lastPicCreateTime - val lastPicPath = MultiFileObserver.lastPicPath - LogUtils.d("发现图片类型应该点击") + MultiFileObserver.createSet.clear() + MultiFileObserver.finishSet.clear() + LogUtils.v("点击图片类型") AccessibilityUtil.performClickWithSon(relativeLayoutContent) AccessibilityExtraUtil.loadingPage("ShowImageController", Constant.CHANGE_PAGE_INTERVAL) - LogUtils.d("发现图片类型 查看图片检查有无新图片产生") - if (MultiFileObserver.lastPicCreateTime > lastPicCreateTime) { + LogUtils.v("发现图片类型 查看图片检查有无新图片产生") + if (MultiFileObserver.createSet.isNotEmpty()) { LogUtils.d("正在下载图片...") var downloading = true val startTime = System.currentTimeMillis() var currentTime = startTime while (currentTime - startTime < Constant.LONG_INTERVAL) { - if (!lastPicPath.equals(MultiFileObserver.lastPicPath)) { - LogUtils.d("下载图片完成") + if (MultiFileObserver.createSet.size == MultiFileObserver.finishSet.size) { + LogUtils.d("下载图片完成: ${MultiFileObserver.createSet.size}") downloading = false try { - val df = SimpleDateFormat("MMdd_HHmmss") - val targetPath = "${ - Utils.getApp().getExternalFilesDir("copy") - }/${df.format(Date())}/${File(MultiFileObserver.lastPicPath).name}.png" - if (FileUtils.copy(MultiFileObserver.lastPicPath, targetPath)) { - LogUtils.d("复制图片完成: $targetPath") - } else { - LogUtils.e("复制图片失败 请检查权限: $targetPath") + for (path in MultiFileObserver.finishSet) { + val df = SimpleDateFormat("MMdd_HHmmss") + val targetPath = "${ + Utils.getApp().getExternalFilesDir("copy") + }/${df.format(Date())}/${File(path).name}.png" + if (FileUtils.copy(path, targetPath)) { + LogUtils.d("复制图片完成: $targetPath " + ImageDepthSizeUtil.checkRawImage(targetPath)) + log("复制图片完成: $targetPath") + } else { + LogUtils.e("复制图片失败 请检查权限: $targetPath") + } } } catch (e: Exception) { LogUtils.e("接收图片出错", e) + error("接收图片出错: ${e.message}") } break } diff --git a/app/src/main/java/org/yameida/worktool/service/WeworkService.kt b/app/src/main/java/org/yameida/worktool/service/WeworkService.kt index ebf72f2..62da9ab 100644 --- a/app/src/main/java/org/yameida/worktool/service/WeworkService.kt +++ b/app/src/main/java/org/yameida/worktool/service/WeworkService.kt @@ -74,12 +74,15 @@ class WeworkService : AccessibilityService() { mFileObserver = MultiFileObserver("/storage/emulated/0/Android/data/com.tencent.wework/files/imagecache/imagemsg2"); mFileObserver?.startWatching() + log("startWatching...") } else { mFileObserver?.stopWatching() mFileObserver?.startWatching() + log("restartWatching...") } } catch (e: Exception) { LogUtils.e(e) + error("initObserver startWatching error!: ${e.message}") } } diff --git a/app/src/main/java/org/yameida/worktool/utils/ImageInfo.java b/app/src/main/java/org/yameida/worktool/utils/ImageInfo.java new file mode 100644 index 0000000..a236a98 --- /dev/null +++ b/app/src/main/java/org/yameida/worktool/utils/ImageInfo.java @@ -0,0 +1,1232 @@ +package org.yameida.worktool.utils; + +/* + * ImageInfo.java + * + * Version 1.9 + * + * A Java class to determine image width, height and color depth for + * a number of image file formats. + * + * Written by Marco Schmidt + * + * Contributed to the Public Domain. + */ + +import java.io.DataInput; +import java.io.FileInputStream; +import java.io.InputStream; +import java.io.IOException; +import java.net.URL; +import java.util.Vector; + +/** + * Get file format, image resolution, number of bits per pixel and optionally + * number of images, comments and physical resolution from + * JPEG, GIF, BMP, PCX, PNG, IFF, RAS, PBM, PGM, PPM and PSD files + * (or input streams). + *

+ * Use the class like this: + *

+ * ImageInfo ii = new ImageInfo();
+ * ii.setInput(in); // in can be InputStream or RandomAccessFile
+ * ii.setDetermineImageNumber(true); // default is false
+ * ii.setCollectComments(true); // default is false
+ * if (!ii.check()) {
+ *   System.err.println("Not a supported image file format.");
+ *   return;
+ * }
+ * System.out.println(ii.getFormatName() + ", " + ii.getMimeType() +
+ *   ", " + ii.getWidth() + " x " + ii.getHeight() + " pixels, " +
+ *   ii.getBitsPerPixel() + " bits per pixel, " + ii.getNumberOfImages() +
+ *   " image(s), " + ii.getNumberOfComments() + " comment(s).");
+ *  // there are other properties, check out the API documentation
+ * 
+ * You can also use this class as a command line program. + * Call it with a number of image file names and URLs as parameters: + *
+ *   java ImageInfo *.jpg *.png *.gif http://somesite.tld/image.jpg
+ * 
+ * or call it without parameters and pipe data to it: + *
+ *   java ImageInfo < image.jpg
+ * 
+ *

+ * Known limitations: + *

    + *
  • When the determination of the number of images is turned off, GIF bits + * per pixel are only read from the global header. + * For some GIFs, local palettes change this to a typically larger + * value. To be certain to get the correct color depth, call + * setDetermineImageNumber(true) before calling check(). + * The complete scan over the GIF file will take additional time.
  • + *
  • Transparency information is not included in the bits per pixel count. + * Actually, it was my decision not to include those bits, so it's a feature! ;-)
  • + *
+ *

+ * Requirements: + *

    + *
  • Java 1.1 or higher
  • + *
+ *

+ * The latest version can be found at http://schmidt.devlib.org/image-info/. + *

+ * Written by Marco Schmidt. + *

+ * This class is contributed to the Public Domain. + * Use it at your own risk. + *

+ * History: + *

    + *
  • 2001-08-24 Initial version.
  • + *
  • 2001-10-13 Added support for the file formats BMP and PCX.
  • + *
  • 2001-10-16 Fixed bug in read(int[], int, int) that returned + *
  • 2002-01-22 Added support for file formats Amiga IFF and Sun Raster (RAS).
  • + *
  • 2002-01-24 Added support for file formats Portable Bitmap / Graymap / Pixmap (PBM, PGM, PPM) and Adobe Photoshop (PSD). + * Added new method getMimeType() to return the MIME type associated with a particular file format.
  • + *
  • 2002-03-15 Added support to recognize number of images in file. Only works with GIF. + * Use {@link #setDetermineImageNumber} with true as argument to identify animated GIFs + * ({@link #getNumberOfImages()} will return a value larger than 1).
  • + *
  • 2002-04-10 Fixed a bug in the feature 'determine number of images in animated GIF' introduced with version 1.1. + * Thanks to Marcelo P. Lima for sending in the bug report. + * Released as 1.1.1.
  • + *
  • 2002-04-18 Added {@link #setCollectComments(boolean)}. + * That new method lets the user specify whether textual comments are to be + * stored in an internal list when encountered in an input image file / stream. + * Added two methods to return the physical width and height of the image in dpi: + * {@link #getPhysicalWidthDpi()} and {@link #getPhysicalHeightDpi()}. + * If the physical resolution could not be retrieved, these methods return -1. + *
  • + *
  • 2002-04-23 Added support for the new properties physical resolution and + * comments for some formats. Released as 1.2.
  • + *
  • 2002-06-17 Added support for SWF, sent in by Michael Aird. + * Changed checkJpeg() so that other APP markers than APP0 will not lead to a failure anymore. + * Released as 1.3.
  • + *
  • 2003-07-28 Bug fix - skip method now takes return values into consideration. + * Less bytes than necessary may have been skipped, leading to flaws in the retrieved information in some cases. + * Thanks to Bernard Bernstein for pointing that out. + * Released as 1.4.
  • + *
  • 2004-02-29 Added support for recognizing progressive JPEG and + * interlaced PNG and GIF. A new method {@link #isProgressive()} returns whether ImageInfo + * has found that the storage type is progressive (or interlaced). + * Thanks to Joe Germuska for suggesting the feature. + * Bug fix: BMP physical resolution is now correctly determined. + * Released as 1.5.
  • + *
  • 2004-11-30 Bug fix: recognizing progressive GIFs + * (interlaced in GIF terminology) did not work (thanks to Franz Jeitler for + * pointing this out). Now it should work, but only if the number of images is determined. + * This is because information on interlacing is stored in a local image header. + * In theory, different images could be stored interlaced and non-interlaced in one + * file. However, I think that's unlikely. Right now, the last image in the GIF file + * that is examined by ImageInfo is used for the "progressive" status.
  • + *
  • 2005-01-02 Some code clean up (unused methods and variables + * commented out, missing javadoc comments, etc.). Thanks to George Sexton for a long list. + * Removed usage of Boolean.toString because + * it's a Java 1.4+ feature (thanks to Gregor Dupont). + * Changed delimiter character in compact output from semicolon to tabulator + * (for better integration with cut(1) and other Unix tools). + * Added some points to the 'Known + * issues' section of the website. + * Released as 1.6.
  • + *
  • 2005-07-26 Removed code to identify Flash (SWF) files. + * Has repeatedly led to problems and support requests, and I don't know the + * format and don't have the time and interest to fix it myself. + * I repeatedly included fixes by others which didn't work for some people. + * I give up on SWF. Please do not contact me about it anymore. + * Set package of ImageInfo class to org.devlib.schmidt.imageinfo (a package + * was repeatedly requested by some users). + * Released as 1.7.
  • + *
  • 2006-02-23 Removed Flash helper methods which weren't used elsewhere. + * Updated skip method which tries "read" whenever "skip(Bytes)" returns a result of 0. + * The old method didn't work with certain input stream types on truncated data streams. + * Thanks to Martin Leidig for reporting this and sending in code. + * Released as 1.8.
  • + * + *
  • 2006-11-13 Removed check that made ImageInfo report JPEG APPx + * markers smaller than 14 bytes as files in unknown format. Such JPEGs seem to be + * generated by Google's Picasa application. First reported with fix by + * Karl von Randow. Released as 1.9.
  • + *
+ * @author Marco Schmidt + */ +public class ImageInfo { + /** + * Return value of {@link #getFormat()} for JPEG streams. + * ImageInfo can extract physical resolution and comments + * from JPEGs (only from APP0 headers). + * Only one image can be stored in a file. + * It is determined whether the JPEG stream is progressive + * (see {@link #isProgressive()}). + */ + public static final int FORMAT_JPEG = 0; + + /** + * Return value of {@link #getFormat()} for GIF streams. + * ImageInfo can extract comments from GIFs and count the number + * of images (GIFs with more than one image are animations). + * It is determined whether the GIF stream is interlaced (see {@link #isProgressive()}). + */ + public static final int FORMAT_GIF = 1; + + /** + * Return value of {@link #getFormat()} for PNG streams. + * PNG only supports one image per file. + * Both physical resolution and comments can be stored with PNG, + * but ImageInfo is currently not able to extract those. + * It is determined whether the PNG stream is interlaced (see {@link #isProgressive()}). + */ + public static final int FORMAT_PNG = 2; + + /** + * Return value of {@link #getFormat()} for BMP streams. + * BMP only supports one image per file. + * BMP does not allow for comments. + * The physical resolution can be stored. + */ + public static final int FORMAT_BMP = 3; + + /** + * Return value of {@link #getFormat()} for PCX streams. + * PCX does not allow for comments or more than one image per file. + * However, the physical resolution can be stored. + */ + public static final int FORMAT_PCX = 4; + + /** + * Return value of {@link #getFormat()} for IFF streams. + */ + public static final int FORMAT_IFF = 5; + + /** + * Return value of {@link #getFormat()} for RAS streams. + * Sun Raster allows for one image per file only and is not able to + * store physical resolution or comments. + */ + public static final int FORMAT_RAS = 6; + + /** Return value of {@link #getFormat()} for PBM streams. */ + public static final int FORMAT_PBM = 7; + + /** Return value of {@link #getFormat()} for PGM streams. */ + public static final int FORMAT_PGM = 8; + + /** Return value of {@link #getFormat()} for PPM streams. */ + public static final int FORMAT_PPM = 9; + + /** Return value of {@link #getFormat()} for PSD streams. */ + public static final int FORMAT_PSD = 10; + +/* public static final int COLOR_TYPE_UNKNOWN = -1; + public static final int COLOR_TYPE_TRUECOLOR_RGB = 0; + public static final int COLOR_TYPE_PALETTED = 1; + public static final int COLOR_TYPE_GRAYSCALE= 2; + public static final int COLOR_TYPE_BLACK_AND_WHITE = 3;*/ + + /** + * The names of all supported file formats. + * The FORMAT_xyz int constants can be used as index values for + * this array. + */ + private static final String[] FORMAT_NAMES = + {"JPEG", "GIF", "PNG", "BMP", "PCX", + "IFF", "RAS", "PBM", "PGM", "PPM", + "PSD"}; + + /** + * The names of the MIME types for all supported file formats. + * The FORMAT_xyz int constants can be used as index values for + * this array. + */ + private static final String[] MIME_TYPE_STRINGS = + {"image/jpeg", "image/gif", "image/png", "image/bmp", "image/pcx", + "image/iff", "image/ras", "image/x-portable-bitmap", "image/x-portable-graymap", "image/x-portable-pixmap", + "image/psd"}; + + private int width; + private int height; + private int bitsPerPixel; + //private int colorType = COLOR_TYPE_UNKNOWN; + private boolean progressive; + private int format; + private InputStream in; + private DataInput din; + private boolean collectComments = true; + private Vector comments; + private boolean determineNumberOfImages; + private int numberOfImages; + private int physicalHeightDpi; + private int physicalWidthDpi; + + private void addComment(String s) { + if (comments == null) { + comments = new Vector(); + } + comments.addElement(s); + } + + /** + * Call this method after you have provided an input stream or file + * using {@link #setInput(InputStream)} or {@link #setInput(DataInput)}. + * If true is returned, the file format was known and information + * on the file's content can be retrieved using the various getXyz methods. + * @return if information could be retrieved from input + */ + public boolean check() { + format = -1; + width = -1; + height = -1; + bitsPerPixel = -1; + numberOfImages = 1; + physicalHeightDpi = -1; + physicalWidthDpi = -1; + comments = null; + try { + int b1 = read() & 0xff; + int b2 = read() & 0xff; + if (b1 == 0x47 && b2 == 0x49) { + return checkGif(); + } + else + if (b1 == 0x89 && b2 == 0x50) { + return checkPng(); + } + else + if (b1 == 0xff && b2 == 0xd8) { + return checkJpeg(); + } + else + if (b1 == 0x42 && b2 == 0x4d) { + return checkBmp(); + } + else + if (b1 == 0x0a && b2 < 0x06) { + return checkPcx(); + } + else + if (b1 == 0x46 && b2 == 0x4f) { + return checkIff(); + } + else + if (b1 == 0x59 && b2 == 0xa6) { + return checkRas(); + } + else + if (b1 == 0x50 && b2 >= 0x31 && b2 <= 0x36) { + return checkPnm(b2 - '0'); + } + else + if (b1 == 0x38 && b2 == 0x42) { + return checkPsd(); + } + else { + return false; + } + } catch (IOException ioe) { + return false; + } + } + + private boolean checkBmp() throws IOException { + byte[] a = new byte[44]; + if (read(a) != a.length) { + return false; + } + width = getIntLittleEndian(a, 16); + height = getIntLittleEndian(a, 20); + if (width < 1 || height < 1) { + return false; + } + bitsPerPixel = getShortLittleEndian(a, 26); + if (bitsPerPixel != 1 && bitsPerPixel != 4 && + bitsPerPixel != 8 && bitsPerPixel != 16 && + bitsPerPixel != 24 && bitsPerPixel != 32) { + return false; + } + int x = (int)(getIntLittleEndian(a, 36) * 0.0254); + if (x > 0) { + setPhysicalWidthDpi(x); + } + int y = (int)(getIntLittleEndian(a, 40) * 0.0254); + if (y > 0) { + setPhysicalHeightDpi(y); + } + format = FORMAT_BMP; + return true; + } + + private boolean checkGif() throws IOException { + final byte[] GIF_MAGIC_87A = {0x46, 0x38, 0x37, 0x61}; + final byte[] GIF_MAGIC_89A = {0x46, 0x38, 0x39, 0x61}; + byte[] a = new byte[11]; // 4 from the GIF signature + 7 from the global header + if (read(a) != 11) { + return false; + } + if ((!equals(a, 0, GIF_MAGIC_89A, 0, 4)) && + (!equals(a, 0, GIF_MAGIC_87A, 0, 4))) { + return false; + } + format = FORMAT_GIF; + width = getShortLittleEndian(a, 4); + height = getShortLittleEndian(a, 6); + int flags = a[8] & 0xff; + bitsPerPixel = ((flags >> 4) & 0x07) + 1; + //progressive = (flags & 0x02) != 0; + if (!determineNumberOfImages) { + return true; + } + // skip global color palette + if ((flags & 0x80) != 0) { + int tableSize = (1 << ((flags & 7) + 1)) * 3; + skip(tableSize); + } + numberOfImages = 0; + int blockType; + do + { + blockType = read(); + switch(blockType) + { + case(0x2c): // image separator + { + if (read(a, 0, 9) != 9) { + return false; + } + flags = a[8] & 0xff; + progressive = (flags & 0x40) != 0; + /*int locWidth = getShortLittleEndian(a, 4); + int locHeight = getShortLittleEndian(a, 6); + System.out.println("LOCAL: " + locWidth + " x " + locHeight);*/ + int localBitsPerPixel = (flags & 0x07) + 1; + if (localBitsPerPixel > bitsPerPixel) { + bitsPerPixel = localBitsPerPixel; + } + if ((flags & 0x80) != 0) { + skip((1 << localBitsPerPixel) * 3); + } + skip(1); // initial code length + int n; + do + { + n = read(); + if (n > 0) { + skip(n); + } + else + if (n == -1) { + return false; + } + } + while (n > 0); + numberOfImages++; + break; + } + case(0x21): // extension + { + int extensionType = read(); + if (collectComments && extensionType == 0xfe) { + StringBuffer sb = new StringBuffer(); + int n; + do + { + n = read(); + if (n == -1) { + return false; + } + if (n > 0) { + for (int i = 0; i < n; i++) { + int ch = read(); + if (ch == -1) { + return false; + } + sb.append((char)ch); + } + } + } + while (n > 0); + } else { + int n; + do + { + n = read(); + if (n > 0) { + skip(n); + } + else + if (n == -1) { + return false; + } + } + while (n > 0); + } + break; + } + case(0x3b): // end of file + { + break; + } + default: + { + return false; + } + } + } + while (blockType != 0x3b); + return true; + } + + private boolean checkIff() throws IOException { + byte[] a = new byte[10]; + // read remaining 2 bytes of file id, 4 bytes file size + // and 4 bytes IFF subformat + if (read(a, 0, 10) != 10) { + return false; + } + final byte[] IFF_RM = {0x52, 0x4d}; + if (!equals(a, 0, IFF_RM, 0, 2)) { + return false; + } + int type = getIntBigEndian(a, 6); + if (type != 0x494c424d && // type must be ILBM... + type != 0x50424d20) { // ...or PBM + return false; + } + // loop chunks to find BMHD chunk + do { + if (read(a, 0, 8) != 8) { + return false; + } + int chunkId = getIntBigEndian(a, 0); + int size = getIntBigEndian(a, 4); + if ((size & 1) == 1) { + size++; + } + if (chunkId == 0x424d4844) { // BMHD chunk + if (read(a, 0, 9) != 9) { + return false; + } + format = FORMAT_IFF; + width = getShortBigEndian(a, 0); + height = getShortBigEndian(a, 2); + bitsPerPixel = a[8] & 0xff; + return (width > 0 && height > 0 && bitsPerPixel > 0 && bitsPerPixel < 33); + } else { + skip(size); + } + } while (true); + } + + private boolean checkJpeg() throws IOException { + byte[] data = new byte[12]; + while (true) { + if (read(data, 0, 4) != 4) { + return false; + } + int marker = getShortBigEndian(data, 0); + int size = getShortBigEndian(data, 2); + if ((marker & 0xff00) != 0xff00) { + return false; // not a valid marker + } + if (marker == 0xffe0) { // APPx + if (size < 14) { + // not an APPx header as we know it, skip + skip(size - 2); + continue; + } + if (read(data, 0, 12) != 12) { + return false; + } + final byte[] APP0_ID = {0x4a, 0x46, 0x49, 0x46, 0x00}; + if (equals(APP0_ID, 0, data, 0, 5)) { + //System.out.println("data 7=" + data[7]); + if (data[7] == 1) { + setPhysicalWidthDpi(getShortBigEndian(data, 8)); + setPhysicalHeightDpi(getShortBigEndian(data, 10)); + } + else + if (data[7] == 2) { + int x = getShortBigEndian(data, 8); + int y = getShortBigEndian(data, 10); + setPhysicalWidthDpi((int)(x * 2.54f)); + setPhysicalHeightDpi((int)(y * 2.54f)); + } + } + skip(size - 14); + } + else + if (collectComments && size > 2 && marker == 0xfffe) { // comment + size -= 2; + byte[] chars = new byte[size]; + if (read(chars, 0, size) != size) { + return false; + } + String comment = new String(chars, "iso-8859-1"); + comment = comment.trim(); + addComment(comment); + } + else + if (marker >= 0xffc0 && marker <= 0xffcf && marker != 0xffc4 && marker != 0xffc8) { + if (read(data, 0, 6) != 6) { + return false; + } + format = FORMAT_JPEG; + bitsPerPixel = (data[0] & 0xff) * (data[5] & 0xff); + progressive = marker == 0xffc2 || marker == 0xffc6 || + marker == 0xffca || marker == 0xffce; + width = getShortBigEndian(data, 3); + height = getShortBigEndian(data, 1); + return true; + } else { + skip(size - 2); + } + } + } + + private boolean checkPcx() throws IOException { + byte[] a = new byte[64]; + if (read(a) != a.length) { + return false; + } + if (a[0] != 1) { // encoding, 1=RLE is only valid value + return false; + } + // width / height + int x1 = getShortLittleEndian(a, 2); + int y1 = getShortLittleEndian(a, 4); + int x2 = getShortLittleEndian(a, 6); + int y2 = getShortLittleEndian(a, 8); + if (x1 < 0 || x2 < x1 || y1 < 0 || y2 < y1) { + return false; + } + width = x2 - x1 + 1; + height = y2 - y1 + 1; + // color depth + int bits = a[1]; + int planes = a[63]; + if (planes == 1 && + (bits == 1 || bits == 2 || bits == 4 || bits == 8)) { + // paletted + bitsPerPixel = bits; + } else + if (planes == 3 && bits == 8) { + // RGB truecolor + bitsPerPixel = 24; + } else { + return false; + } + setPhysicalWidthDpi(getShortLittleEndian(a, 10)); + setPhysicalHeightDpi(getShortLittleEndian(a, 10)); + format = FORMAT_PCX; + return true; + } + + private boolean checkPng() throws IOException { + final byte[] PNG_MAGIC = {0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a}; + byte[] a = new byte[27]; + if (read(a) != 27) { + return false; + } + if (!equals(a, 0, PNG_MAGIC, 0, 6)) { + return false; + } + format = FORMAT_PNG; + width = getIntBigEndian(a, 14); + height = getIntBigEndian(a, 18); + bitsPerPixel = a[22] & 0xff; + int colorType = a[23] & 0xff; + if (colorType == 2 || colorType == 6) { + bitsPerPixel *= 3; + } + progressive = (a[26] & 0xff) != 0; + return true; + } + + private boolean checkPnm(int id) throws IOException { + if (id < 1 || id > 6) { + return false; + } + final int[] PNM_FORMATS = {FORMAT_PBM, FORMAT_PGM, FORMAT_PPM}; + format = PNM_FORMATS[(id - 1) % 3]; + boolean hasPixelResolution = false; + String s; + while (true) + { + s = readLine(); + if (s != null) { + s = s.trim(); + } + if (s == null || s.length() < 1) { + continue; + } + if (s.charAt(0) == '#') { // comment + if (collectComments && s.length() > 1) { + addComment(s.substring(1)); + } + continue; + } + if (!hasPixelResolution) { // split "343 966" into width=343, height=966 + int spaceIndex = s.indexOf(' '); + if (spaceIndex == -1) { + return false; + } + String widthString = s.substring(0, spaceIndex); + spaceIndex = s.lastIndexOf(' '); + if (spaceIndex == -1) { + return false; + } + String heightString = s.substring(spaceIndex + 1); + try { + width = Integer.parseInt(widthString); + height = Integer.parseInt(heightString); + } catch (NumberFormatException nfe) { + return false; + } + if (width < 1 || height < 1) { + return false; + } + if (format == FORMAT_PBM) { + bitsPerPixel = 1; + return true; + } + hasPixelResolution = true; + } + else + { + int maxSample; + try { + maxSample = Integer.parseInt(s); + } catch (NumberFormatException nfe) { + return false; + } + if (maxSample < 0) { + return false; + } + for (int i = 0; i < 25; i++) { + if (maxSample < (1 << (i + 1))) { + bitsPerPixel = i + 1; + if (format == FORMAT_PPM) { + bitsPerPixel *= 3; + } + return true; + } + } + return false; + } + } + } + + private boolean checkPsd() throws IOException { + byte[] a = new byte[24]; + if (read(a) != a.length) { + return false; + } + final byte[] PSD_MAGIC = {0x50, 0x53}; + if (!equals(a, 0, PSD_MAGIC, 0, 2)) { + return false; + } + format = FORMAT_PSD; + width = getIntBigEndian(a, 16); + height = getIntBigEndian(a, 12); + int channels = getShortBigEndian(a, 10); + int depth = getShortBigEndian(a, 20); + bitsPerPixel = channels * depth; + return (width > 0 && height > 0 && bitsPerPixel > 0 && bitsPerPixel <= 64); + } + + private boolean checkRas() throws IOException { + byte[] a = new byte[14]; + if (read(a) != a.length) { + return false; + } + final byte[] RAS_MAGIC = {0x6a, (byte)0x95}; + if (!equals(a, 0, RAS_MAGIC, 0, 2)) { + return false; + } + format = FORMAT_RAS; + width = getIntBigEndian(a, 2); + height = getIntBigEndian(a, 6); + bitsPerPixel = getIntBigEndian(a, 10); + return (width > 0 && height > 0 && bitsPerPixel > 0 && bitsPerPixel <= 24); + } + + /** + * Run over String list, return false iff at least one of the arguments + * equals -c. + * @param args string list to check + */ + private static boolean determineVerbosity(String[] args) { + if (args != null && args.length > 0) { + for (int i = 0; i < args.length; i++) { + if ("-c".equals(args[i])) { + return false; + } + } + } + return true; + } + + private static boolean equals(byte[] a1, int offs1, byte[] a2, int offs2, int num) { + while (num-- > 0) { + if (a1[offs1++] != a2[offs2++]) { + return false; + } + } + return true; + } + + /** + * If {@link #check()} was successful, returns the image's number of bits per pixel. + * Does not include transparency information like the alpha channel. + * @return number of bits per image pixel + */ + public int getBitsPerPixel() { + return bitsPerPixel; + } + + /** + * Returns the index'th comment retrieved from the file. + * @param index int index of comment to return + * @throws IllegalArgumentException if index is smaller than 0 or larger than or equal + * to the number of comments retrieved + * @see #getNumberOfComments + */ + public String getComment(int index) { + if (comments == null || index < 0 || index >= comments.size()) { + throw new IllegalArgumentException("Not a valid comment index: " + index); + } + return (String)comments.elementAt(index); + } + + /** + * If {@link #check()} was successful, returns the image format as one + * of the FORMAT_xyz constants from this class. + * Use {@link #getFormatName()} to get a textual description of the file format. + * @return file format as a FORMAT_xyz constant + */ + public int getFormat() { + return format; + } + + /** + * If {@link #check()} was successful, returns the image format's name. + * Use {@link #getFormat()} to get a unique number. + * @return file format name + */ + public String getFormatName() { + if (format >= 0 && format < FORMAT_NAMES.length) { + return FORMAT_NAMES[format]; + } else { + return "?"; + } + } + + /** + * If {@link #check()} was successful, returns one the image's vertical + * resolution in pixels. + * @return image height in pixels + */ + public int getHeight() { + return height; + } + + private static int getIntBigEndian(byte[] a, int offs) { + return + (a[offs] & 0xff) << 24 | + (a[offs + 1] & 0xff) << 16 | + (a[offs + 2] & 0xff) << 8 | + a[offs + 3] & 0xff; + } + + private static int getIntLittleEndian(byte[] a, int offs) { + return + (a[offs + 3] & 0xff) << 24 | + (a[offs + 2] & 0xff) << 16 | + (a[offs + 1] & 0xff) << 8 | + a[offs] & 0xff; + } + + /** + * If {@link #check()} was successful, returns a String with the + * MIME type of the format. + * @return MIME type, e.g. image/jpeg + */ + public String getMimeType() { + if (format >= 0 && format < MIME_TYPE_STRINGS.length) { + if (format == FORMAT_JPEG && progressive) + { + return "image/pjpeg"; + } + return MIME_TYPE_STRINGS[format]; + } else { + return null; + } + } + + /** + * If {@link #check()} was successful and {@link #setCollectComments(boolean)} was called with + * true as argument, returns the number of comments retrieved + * from the input image stream / file. + * Any number >= 0 and smaller than this number of comments is then a + * valid argument for the {@link #getComment(int)} method. + * @return number of comments retrieved from input image + */ + public int getNumberOfComments() + { + if (comments == null) { + return 0; + } else { + return comments.size(); + } + } + + /** + * Returns the number of images in the examined file. + * Assumes that setDetermineImageNumber(true); was called before + * a successful call to {@link #check()}. + * This value can currently be only different from 1 for GIF images. + * @return number of images in file + */ + public int getNumberOfImages() + { + return numberOfImages; + } + + /** + * Returns the physical height of this image in dots per inch (dpi). + * Assumes that {@link #check()} was successful. + * Returns -1 on failure. + * @return physical height (in dpi) + * @see #getPhysicalWidthDpi() + * @see #getPhysicalHeightInch() + */ + public int getPhysicalHeightDpi() { + return physicalHeightDpi; + } + + /** + * If {@link #check()} was successful, returns the physical width of this image in dpi (dots per inch) + * or -1 if no value could be found. + * @return physical height (in dpi) + * @see #getPhysicalHeightDpi() + * @see #getPhysicalWidthDpi() + * @see #getPhysicalWidthInch() + */ + public float getPhysicalHeightInch() { + int h = getHeight(); + int ph = getPhysicalHeightDpi(); + if (h > 0 && ph > 0) { + return ((float)h) / ((float)ph); + } else { + return -1.0f; + } + } + + /** + * If {@link #check()} was successful, returns the physical width of this image in dpi (dots per inch) + * or -1 if no value could be found. + * @return physical width (in dpi) + * @see #getPhysicalHeightDpi() + * @see #getPhysicalWidthInch() + * @see #getPhysicalHeightInch() + */ + public int getPhysicalWidthDpi() { + return physicalWidthDpi; + } + + /** + * Returns the physical width of an image in inches, or + * -1.0f if width information is not available. + * Assumes that {@link #check} has been called successfully. + * @return physical width in inches or -1.0f on failure + * @see #getPhysicalWidthDpi + * @see #getPhysicalHeightInch + */ + public float getPhysicalWidthInch() { + int w = getWidth(); + int pw = getPhysicalWidthDpi(); + if (w > 0 && pw > 0) { + return ((float)w) / ((float)pw); + } else { + return -1.0f; + } + } + + private static int getShortBigEndian(byte[] a, int offs) { + return + (a[offs] & 0xff) << 8 | + (a[offs + 1] & 0xff); + } + + private static int getShortLittleEndian(byte[] a, int offs) { + return (a[offs] & 0xff) | (a[offs + 1] & 0xff) << 8; + } + + /** + * If {@link #check()} was successful, returns one the image's horizontal + * resolution in pixels. + * @return image width in pixels + */ + public int getWidth() { + return width; + } + + /** + * Returns whether the image is stored in a progressive (also called: interlaced) way. + * @return true for progressive/interlaced, false otherwise + */ + public boolean isProgressive() + { + return progressive; + } + + /** + * To use this class as a command line application, give it either + * some file names as parameters (information on them will be + * printed to standard output, one line per file) or call + * it with no parameters. It will then check data given to it + * via standard input. + * @param args the program arguments which must be file names + */ +// public static void main(String[] args) { +// ImageInfo imageInfo = new ImageInfo(); +// imageInfo.setDetermineImageNumber(true); +// boolean verbose = determineVerbosity(args); +// if (args.length == 0) { +// run(null, System.in, imageInfo, verbose); +// } else { +// int index = 0; +// while (index < args.length) { +// InputStream in = null; +// try { +// String name = args[index++]; +// System.out.print(name + ";"); +// if (name.startsWith("http://")) { +// in = new URL(name).openConnection().getInputStream(); +// } else { +// in = new FileInputStream(name); +// } +// run(name, in, imageInfo, verbose); +// in.close(); +// } catch (IOException e) { +// System.out.println(e); +// try { +// if (in != null) { +// in.close(); +// } +// } catch (IOException ee) { +// } +// } +// } +// } +// } + + private static void print(String sourceName, ImageInfo ii, boolean verbose) { + if (verbose) { + printVerbose(sourceName, ii); + } else { + printCompact(sourceName, ii); + } + } + + private static void printCompact(String sourceName, ImageInfo imageInfo) { + final String SEP = "\t"; + System.out.println( + sourceName + SEP + + imageInfo.getFormatName() + SEP + + imageInfo.getMimeType() + SEP + + imageInfo.getWidth() + SEP + + imageInfo.getHeight() + SEP + + imageInfo.getBitsPerPixel() + SEP + + imageInfo.getNumberOfImages() + SEP + + imageInfo.getPhysicalWidthDpi() + SEP + + imageInfo.getPhysicalHeightDpi() + SEP + + imageInfo.getPhysicalWidthInch() + SEP + + imageInfo.getPhysicalHeightInch() + SEP + + imageInfo.isProgressive() + ); + } + + private static void printLine(int indentLevels, String text, float value, float minValidValue) { + if (value < minValidValue) { + return; + } + printLine(indentLevels, text, Float.toString(value)); + } + + private static void printLine(int indentLevels, String text, int value, int minValidValue) { + if (value >= minValidValue) { + printLine(indentLevels, text, Integer.toString(value)); + } + } + + private static void printLine(int indentLevels, String text, String value) { + if (value == null || value.length() == 0) { + return; + } + while (indentLevels-- > 0) { + System.out.print("\t"); + } + if (text != null && text.length() > 0) { + System.out.print(text); + System.out.print(" "); + } + System.out.println(value); + } + + private static void printVerbose(String sourceName, ImageInfo ii) { + printLine(0, null, sourceName); + printLine(1, "File format: ", ii.getFormatName()); + printLine(1, "MIME type: ", ii.getMimeType()); + printLine(1, "Width (pixels): ", ii.getWidth(), 1); + printLine(1, "Height (pixels): ", ii.getHeight(), 1); + printLine(1, "Bits per pixel: ", ii.getBitsPerPixel(), 1); + printLine(1, "Progressive: ", ii.isProgressive() ? "yes" : "no"); + printLine(1, "Number of images: ", ii.getNumberOfImages(), 1); + printLine(1, "Physical width (dpi): ", ii.getPhysicalWidthDpi(), 1); + printLine(1, "Physical height (dpi): ", ii.getPhysicalHeightDpi(), 1); + printLine(1, "Physical width (inches): ", ii.getPhysicalWidthInch(), 1.0f); + printLine(1, "Physical height (inches): ", ii.getPhysicalHeightInch(), 1.0f); + int numComments = ii.getNumberOfComments(); + printLine(1, "Number of textual comments: ", numComments, 1); + if (numComments > 0) { + for (int i = 0; i < numComments; i++) { + printLine(2, null, ii.getComment(i)); + } + } + } + + private int read() throws IOException { + if (in != null) { + return in.read(); + } else { + return din.readByte(); + } + } + + private int read(byte[] a) throws IOException { + if (in != null) { + return in.read(a); + } else { + din.readFully(a); + return a.length; + } + } + + private int read(byte[] a, int offset, int num) throws IOException { + if (in != null) { + return in.read(a, offset, num); + } else { + din.readFully(a, offset, num); + return num; + } + } + + private String readLine() throws IOException { + return readLine(new StringBuffer()); + } + + private String readLine(StringBuffer sb) throws IOException { + boolean finished; + do { + int value = read(); + finished = (value == -1 || value == 10); + if (!finished) { + sb.append((char)value); + } + } while (!finished); + return sb.toString(); + } + + public static void run(String sourceName, InputStream in, ImageInfo imageInfo, boolean verbose) { + imageInfo.setInput(in); + imageInfo.setDetermineImageNumber(true); + imageInfo.setCollectComments(verbose); + if (imageInfo.check()) { + print(sourceName, imageInfo, verbose); + } + } + + /** + * Specify whether textual comments are supposed to be extracted from input. + * Default is false. + * If enabled, comments will be added to an internal list. + * @param newValue if true, this class will read comments + * @see #getNumberOfComments + * @see #getComment + */ + public void setCollectComments(boolean newValue) + { + collectComments = newValue; + } + + /** + * Specify whether the number of images in a file is to be + * determined - default is false. + * This is a special option because some file formats require running over + * the entire file to find out the number of images, a rather time-consuming + * task. + * Not all file formats support more than one image. + * If this method is called with true as argument, + * the actual number of images can be queried via + * {@link #getNumberOfImages()} after a successful call to + * {@link #check()}. + * @param newValue will the number of images be determined? + * @see #getNumberOfImages + */ + public void setDetermineImageNumber(boolean newValue) + { + determineNumberOfImages = newValue; + } + + /** + * Set the input stream to the argument stream (or file). + * Note that {@link java.io.RandomAccessFile} implements + * {@link java.io.DataInput}. + * @param dataInput the input stream to read from + */ + public void setInput(DataInput dataInput) { + din = dataInput; + in = null; + } + + /** + * Set the input stream to the argument stream (or file). + * @param inputStream the input stream to read from + */ + public void setInput(InputStream inputStream) { + in = inputStream; + din = null; + } + + private void setPhysicalHeightDpi(int newValue) { + physicalWidthDpi = newValue; + } + + private void setPhysicalWidthDpi(int newValue) { + physicalHeightDpi = newValue; + } + + private void skip(int num) throws IOException { + while (num > 0) { + long result; + if (in != null) { + result = in.skip(num); + } else { + result = din.skipBytes(num); + } + if (result > 0) { + num -= result; + } else { + if (in != null) { + result = in.read(); + } else { + result = din.readByte(); + } + if (result == -1) { + throw new IOException("Premature end of input."); + } else { + num--; + } + } + } + } +} \ No newline at end of file