package com.newsrob.util;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Pattern;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.Bitmap.Config;
import android.graphics.PorterDuff.Mode;
import com.newsrob.PL;
public class PreviewGenerator {
private File assetsDir;
private int targetHeight;
private int targetWidth;
private int roundedCornerRadiusPx;
private Context context;
private static final Pattern invalidExtensionPattern = Pattern.compile(".+[.]htm.nr$", Pattern.CASE_INSENSITIVE);
private static final Filter filter = new Filter();
private static final FileLengthComparator comparator = new FileLengthComparator();
public PreviewGenerator(Context ctx, File assetsDir, int targetWidth, int targetHeight, int roundedCornerRadiusPx) {
this.assetsDir = assetsDir;
this.targetHeight = targetHeight;
this.targetWidth = targetWidth;
this.roundedCornerRadiusPx = roundedCornerRadiusPx;
this.context = ctx;
}
/**
* Can only be called to downscale images, never upscale
*/
public static Rect getScaledRectangle(int srcWidth, int srcHeight, int dstWidth, int dstHeight) {
final double widthFactor = srcWidth * 1.0f / dstWidth;
final double heightFactor = srcHeight * 1.0f / dstHeight;
// Prefer the dimension that needs to be
// scaled less ... and consequently later(!) on
// cut off the excess size of the other dimension
final double scaleFactor = Math.min(widthFactor, heightFactor);
// if (scaleFactor < 1.0f)
// throw new
// RuntimeException("getDestinationSizeAndOffset can only be used to downscale dimensions, not up.");
final int excessWidth = (int) ((srcWidth - (dstWidth * scaleFactor)));
final int excessHeight = (int) ((srcHeight - (dstHeight * scaleFactor)));
final Rect r = new Rect();
r.left = excessWidth / 2;
r.top = excessHeight / 2;
r.bottom = srcHeight - r.top;
r.right = srcWidth - r.left;
return r;
}
/**
* @return true when a preview was generated.
*/
public boolean generatePreview() {
Timing t = new Timing("Generate preview.", context);
try {
File image = findBiggestImageFile();
if (image == null) {
PL.log("PreviewGenerator: No suitable biggest image file found.", context);
return false;
}
return doGeneratePreview(image);
} finally {
t.stop();
}
}
protected List<File> findAllImageFiles() {
return Arrays.asList(assetsDir.listFiles(filter));
}
protected File findBiggestImageFile() {
List<File> allImages = findAllImageFiles();
if (allImages.isEmpty())
return null;
Collections.sort(allImages, comparator);
// copy only images that are bigger than x bytes
List<File> allImages2 = new ArrayList<File>(allImages.size());
for (File file : allImages) {
if (file.length() > getMinSizeInBytes()) {
int height = -1;
int width = -1;
synchronized (PreviewGenerator.class) {
// using the class based synchronization to prevent to much
// memory usage
Bitmap bm = null;
try {
bm = BitmapFactory.decodeFile(file.getAbsolutePath(), null);
} catch (OutOfMemoryError ooe) {
PL.log("PreviewGenerator: " + file.getAbsolutePath()
+ " was too big (OOM) in findingBiggestImageFile. Skipping it. " + getMemoryStatus(),
context);
}
if (bm == null)
continue;
width = bm.getWidth();
height = bm.getHeight();
bm.recycle();
}
if (width >= targetWidth / 2 && height >= targetHeight / 2)
allImages2.add(file);
else
PL.log("PreviewGenerator: Skipped " + file + " because it was only " + width + " x " + height
+ ". Looking for at least " + targetWidth / 2 + " x " + targetHeight / 2, context);
} else
PL.log("PreviewGenerator: Skipped " + file + " because it is too small.", context);
}
if (allImages2.isEmpty())
return null;
return allImages2.get(allImages2.size() - 1);
}
private String getMemoryStatus() {
Runtime rt = Runtime.getRuntime();
return String.format("-- Memory free: %4.2fMB total: %4.2fMB max: %4.2fMB\n", rt.freeMemory() / 1024 / 1024.0,
rt.totalMemory() / 1024 / 1024.0, rt.maxMemory() / 1024 / 1024.0);
}
protected static int getMinSizeInBytes() {
return 2048;
}
protected static int getMaxSizeInBytes() {
return 2048 * 1024;
}
/**
* @return true when a preview was generated.
*/
protected boolean doGeneratePreview(File fromImage) {
BitmapFactory.Options options = new BitmapFactory.Options();
// options.inScaled = true;
// options.outWidth = this.targetWidth;
// options.outHeight = this.targetHeight;
int fileSize = (int) fromImage.length();
int sampleFactor = 1;
while (fileSize / sampleFactor > 500 * 1024)
sampleFactor *= 2;
PL.log("Sampling " + fromImage.getAbsolutePath() + " size=" + (fromImage.length() / 1024) + " scaleFactor="
+ sampleFactor + " Resulting size= " + (fromImage.length() / 1024 / sampleFactor), context);
options.inSampleSize = sampleFactor;
File outputFile = new File(assetsDir, "preview.pngnr");
try {
synchronized (PreviewGenerator.class) {
// using the class based synchronization to prevent to much
// memory usage
FileOutputStream fos = new FileOutputStream(outputFile);
Bitmap inBm = BitmapFactory.decodeFile(fromImage.getAbsolutePath(), options);
Rect r = getScaledRectangle(inBm.getWidth(), inBm.getHeight(), this.targetWidth, this.targetHeight);
Bitmap cutoffScaledBitmap = Bitmap
.createBitmap(inBm, r.left, r.top, r.right - r.left, r.bottom - r.top);
Bitmap outBm = Bitmap.createScaledBitmap(cutoffScaledBitmap, this.targetWidth, targetHeight, true);
cutoffScaledBitmap.recycle();
cutoffScaledBitmap = null;
inBm.recycle();
inBm = null;
Bitmap roundedBm = getRoundedCornerBitmap(outBm);
outBm.recycle();
outBm = null;
roundedBm.compress(CompressFormat.PNG, 100, fos);
fos.close();
// inBm.recycle();
// outBm.recycle();
// cutoffScaledBitmap.recycle();
roundedBm.recycle();
}
}
catch (IOException e) {
e.printStackTrace();
if (outputFile != null)
PL.log("Deleting output file: " + outputFile.delete(), context);
return false;
} catch (OutOfMemoryError ooe) {
ooe.printStackTrace();
PL.log(
"PreviewGenerator: Bitmap was too big (OOM) in generatingPreview. Skipping it. "
+ getMemoryStatus(), context);
if (outputFile != null)
PL.log("Deleting output file: " + outputFile.delete(), context);
return false;
}
return true;
}
private Bitmap getRoundedCornerBitmap(Bitmap bitmap) {
Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Config.ARGB_8888);
Canvas canvas = new Canvas(output);
canvas.drawARGB(0, 0, 0, 0);
final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
final RectF rectF = new RectF(rect);
final int color = 0xffffffff;
final Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(color);
canvas.drawRoundRect(rectF, roundedCornerRadiusPx, roundedCornerRadiusPx, paint);
paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
canvas.drawBitmap(bitmap, 0, 0, paint);
return output;
}
static class Filter implements FilenameFilter {
@Override
public boolean accept(File dir, String filename) {
boolean validExtension = !invalidExtensionPattern.matcher(filename).find();// true
// ||
// validExtensionsPattern.matcher(filename).find();
if (!validExtension)
return false;
long sizeBytes = new File(dir, filename).length();
boolean rightSize = (sizeBytes < getMaxSizeInBytes()) && sizeBytes > getMinSizeInBytes(); // Only
// check
// files
// smaller
// than
// 150
// KB.
if (!rightSize)
return false;
boolean fileMightBeAd = !filename.toLowerCase().contains("_ad");
return fileMightBeAd;
}
}
/**
*
* Bigger files are bigger than smaller files.
*
*/
static class FileLengthComparator implements Comparator<File> {
@Override
public int compare(File f1, File f2) {
return (int) (f1.length() - f2.length());
}
}
}