package info.papdt.pano.processor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.BitmapRegionDecoder; import android.graphics.Canvas; import android.graphics.Rect; import android.util.Log; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.Arrays; import java.util.ArrayList; import java.util.List; import java.util.zip.CRC32; import info.papdt.pano.support.FastBitmapReader; import info.papdt.pano.support.FastBitmapWriter; import static info.papdt.pano.BuildConfig.DEBUG; import static info.papdt.pano.support.Utility.*; import static info.papdt.pano.support.BitmapUtility.*; /* * To compose a list of screenshots into one * But preserving the areas where they are no difference * */ public class ScreenshotComposer { private static final String TAG = ScreenshotComposer.class.getSimpleName(); //private static final String SEPERATOR = ","; static class Region { int startLine; int endLine; } public static interface ProgressListener { void onAnalyzingImage(int i, int j, int total); void onComposingImage(); } private static ScreenshotComposer sInstance; private String mOutDir = "/sdcard/Pictures/Panoramic"; private float mThreshold = 0.08f; private int mStatusBarHeight = 40; // px private int mShadowHeight = 10; // px public static final ScreenshotComposer getInstance() { if (sInstance == null) { sInstance = new ScreenshotComposer(); } return sInstance; } private ScreenshotComposer() { } public void setOutputDir(String opt) { mOutDir = opt; File out = new File(mOutDir); if (!out.exists()) { out.mkdirs(); out.mkdir(); } } public void setThreshold(float threshold) { if (threshold > 1 || threshold < 0) throw new IllegalArgumentException("illegal threshold"); mThreshold = threshold; } public void setStatusBarHeight(int height) { mStatusBarHeight = height; } public void setShadowHeight(int height) { mShadowHeight = height; if (DEBUG) { Log.d(TAG, "shadow height " + mShadowHeight); } } public String compose(String[] images, ProgressListener listener) { File[] files = new File[images.length]; for (int i = 0; i < images.length; i++) { files[i] = new File(images[i]); if (DEBUG) { Log.d(TAG, "Adding " + images[i]); } if (!files[i].exists()) { if (DEBUG) { Log.d(TAG, images[i] + " not exist"); } return null; } } return compose(files, listener); } public String compose(File[] images, ProgressListener listener) { Region[] regions = new Region[images.length]; FastBitmapReader currentBmp = null, nextBmp = null; int fullHeight = 0, fullWidth = 0; for (int i = 0; i < images.length - 1; i++) { long thisStartTime = System.currentTimeMillis(); if (listener != null) { listener.onAnalyzingImage(i + 1, i + 2, images.length); } long decodeStartTime = System.currentTimeMillis(); // Intented to use thresholding but failed. Help needed. if (currentBmp == null) { currentBmp = new FastBitmapReader(BitmapFactory.decodeFile(images[i].getAbsolutePath())); fullWidth = currentBmp.getWidth(); } nextBmp = new FastBitmapReader(BitmapFactory.decodeFile(images[i + 1].getAbsolutePath())); if (DEBUG) { Log.d(TAG, "decode time " + (System.currentTimeMillis() - decodeStartTime)); } if (currentBmp.getHeight() != nextBmp.getHeight()) { if (DEBUG) { Log.d(TAG, "Height different"); } return null; } // First, find the max different region of the two bitmaps long headStartTime = System.currentTimeMillis(); // Go through from the first line int start = -1; for (int j = mStatusBarHeight + 1; j < currentBmp.getHeight(); j++) { if (compareLines(currentBmp, nextBmp, j)) { start = j; break; } } if (start == -1) { if (DEBUG) { Log.d(TAG, "start line not found"); } return null; } // Go through from the last line int end = -1; for (int j = currentBmp.getHeight() - 1; j > start; j--) { if (compareLines(currentBmp, nextBmp, j)) { end = j; break; } } if (end == -1) { if (DEBUG) { Log.d(TAG, "End line not found"); } return null; } if (DEBUG) { Log.d(TAG, "head match time " + (System.currentTimeMillis() - headStartTime)); } if (i == 0) { Region region = new Region(); region.startLine = start; region.endLine = end; regions[0] = region; fullHeight += currentBmp.getHeight(); } // Second, find out the max common region // Generate a hash string of the bitmaps long regionStartTime = System.currentTimeMillis(); Integer[] hashCurrent = buildHashOfRegion(currentBmp, start + mShadowHeight, end); Integer[] hashNext = buildHashOfRegion(nextBmp, start + mShadowHeight, end); if (DEBUG) { Log.d(TAG, "region build time " + (System.currentTimeMillis() - regionStartTime)); } /*if (DEBUG) { Log.d(TAG, "hashCurrent = " + hashCurrent); Log.d(TAG, "hashNext = " + hashNext); }*/ int length = end - start - mShadowHeight - 1; /*String matchSub = null;*/ List<Long> matchSub = null; int matchLength = arrayHeadTailMatch(hashNext, hashCurrent, length, mThreshold); /*for (int j = length; j > 0; j--) { //List<Long> hashSubNext = buildHashOfSubregion(hashNext, j); //List<Long> hashSubCur = buildHashOfSubregionFromBottom(hashCurrent, j); //int result = arrayContainsEx(hashCurrent, hashSub, mThreshold); // if (arrayCompareEx(hashNext, hashCurrent, 0, hashCurrent.size() - j - 1, j, mThreshold)) { //matchSub = hashSubNext; // matchLength = j; // break; // } /*String hashSub = buildHashStringOfSubregion(hashNext, j); if (hashCurrent.contains(hashSub)) { matchSub = hashSub; matchLength = j; break; }*/ //} /*if (matchSub == null) { return null; }*/ //int index = arrayContainsEx(hashNext, matchSub, 0.0f); start = start/* + index*/ + matchLength + mShadowHeight + 1; if (DEBUG) { Log.d(TAG, "Different region between " + i + " and " + (i + 1)); Log.d(TAG, start + "-" + end); } Region region = new Region(); region.startLine = start; region.endLine = end; regions[i + 1] = region; fullHeight += end - start; if (DEBUG) { Log.d(TAG, i + " total time " + (System.currentTimeMillis() - thisStartTime)); } currentBmp.recycle(); currentBmp = nextBmp; nextBmp = null; } currentBmp.recycle(); currentBmp = null; /*currentBmp = BitmapFactory.decodeFile(images[0].getAbsolutePath()); nextBmp = BitmapFactory.decodeFile(images[1].getAbsolutePath()); if (DEBUG) { Log.d(TAG, "hash1: " + getHashOfLine(currentBmp, 150)); Log.d(TAG, "hash2: " + getHashOfLine(nextBmp, 150)); }*/ if (DEBUG) { Log.d(TAG, "fullHeight = " + fullHeight); } if (listener != null) { listener.onComposingImage(); } //Bitmap out = Bitmap.createBitmap(fullWidth, fullHeight, Bitmap.Config.ARGB_8888); //Canvas canvas = new Canvas(out); FastBitmapWriter writer = new FastBitmapWriter(fullWidth, fullHeight); int totalHeight = 0; FastBitmapReader bmp; BitmapRegionDecoder decoder; for (int i = 0; i < images.length; i++) { //bmp = new FastBitmapReader(BitmapFactory.decodeFile(images[i].getAbsolutePath())); try { decoder = BitmapRegionDecoder.newInstance(images[i].getAbsolutePath(), false); } catch (IOException e) { throw new RuntimeException(e); } Region region = regions[i]; //int height = currentBmp.getHeight(); //Rect rect = new Rect(0, 0, fullWidth, fullHeight); /*Rect src = new Rect(); Rect dst = new Rect(); src.left = 0; src.right = fullWidth; dst.left = 0; dst.right = fullWidth;*/ if (i == 0) { /*src.top = 0; src.bottom = region.endLine; dst.top = 0; dst.bottom = src.bottom;*/ bmp = new FastBitmapReader(decoder.decodeRegion(new Rect(0, 0, fullWidth, region.endLine), null)); writer.writeBitmapRegion(bmp, 0, 0, region.endLine); bmp.recycle(); //canvas.drawBitmap(bmp, src, dst, null); /*src.top = region.endLine; src.bottom = bmp.getHeight(); dst.bottom = fullHeight; dst.top = dst.bottom - (src.bottom - src.top);*/ int bmpHeight = decoder.getHeight(); if (region.endLine < bmpHeight - 1) { // Dirty Fix: Ignore any NPE here. try { bmp = new FastBitmapReader(decoder.decodeRegion(new Rect(0, region.endLine, fullWidth, bmpHeight), null)); writer.writeBitmapRegion(bmp, 0, fullHeight - (bmpHeight - region.endLine), bmpHeight - region.endLine); bmp.recycle(); } catch (NullPointerException npe) { } } //canvas.drawBitmap(bmp, src, dst, null); totalHeight += region.endLine; } else { /*src.top = region.startLine; src.bottom = region.endLine; dst.top = totalHeight; dst.bottom = dst.top + (src.bottom - src.top);*/ //canvas.drawBitmap(bmp, src, dst, null); bmp = new FastBitmapReader(decoder.decodeRegion(new Rect(0, region.startLine, fullWidth, region.endLine), null)); writer.writeBitmapRegion(bmp, 0, totalHeight, region.endLine - region.startLine); bmp.recycle(); totalHeight += (region.endLine - region.startLine); } decoder.recycle(); } // Write File outFile = new File(mOutDir + "/Panoramic-" + System.currentTimeMillis() + ".png"); if (outFile.exists()) { outFile.delete(); } try { outFile.createNewFile(); } catch (IOException e) { } Bitmap out = writer.getBitmap(); writer.recycle(); FileOutputStream opt = null; try { opt = new FileOutputStream(outFile); } catch (IOException e) { } if (opt != null) { out.compress(Bitmap.CompressFormat.PNG, 100, opt); } try { opt.close(); } catch (IOException e) { } out.recycle(); return outFile.getAbsolutePath(); } // True = different private boolean compareLines(FastBitmapReader bmp1, FastBitmapReader bmp2, int line) { int diff = 0; for (int i = 0; i < bmp1.getWidth(); i++) { if (bmp1.getPixel(i, line) != bmp2.getPixel(i, line)) { diff++; } } return diff > bmp1.getWidth() / 10 * mThreshold; } private int getHashOfLine(FastBitmapReader bmp, int line) { int start = line * bmp.getWidth(); int end = start + bmp.getWidth(); int[] pixels = Arrays.copyOfRange(bmp.getPixels(), start, end); return Arrays.hashCode(pixels); } private Integer[] buildHashOfRegion(FastBitmapReader bmp, final int start, final int end) { //List<Long> list = new ArrayList<Long>(); Integer[] array = new Integer[end - start]; /*for (int i = start; i < end; i++) { array[i - start] = getHashOfLine(bmp, i); }*/ return new MultiThreadTask<FastBitmapReader, Integer>(bmp, array) { @Override protected void doExecute(FastBitmapReader arg, int taskStart, int taskLength) { for (int i = 0; i < taskLength; i++) { int hash = getHashOfLine(arg, start + taskStart + i); setResult(taskStart + i, hash); } } }.execute(Runtime.getRuntime().availableProcessors()); // CPU cores as threads //return array; } }