图片解析工具类,就是从文件读取图片内容,并处理成我们需要的样式(大小,旋转,修剪等)。
该工具类别看这么的长,实际上就一个函数,那就是
decodeFileWithLock
所有其它的函数都是在为这个函数做服务呢,或者给这个函数做帮手呢。
这样再来看该类,就简单多了。
源码:
/**
* Created by wyouflf on 15/10/9.
* ImageDecoder for ImageLoader
*/
public final class ImageDecoder {
private final static int BITMAP_DECODE_MAX_WORKER;//处理器最大的数量
private final static AtomicInteger bitmapDecodeWorker = new AtomicInteger(0);//锁
private final static Object bitmapDecodeLock = new Object();
private final static Object gifDecodeLock = new Object();//gif锁
private final static byte[] GIF_HEADER = new byte[]{'G', 'I', 'F'};//git文件的头文件标识符
private final static byte[] WEBP_HEADER = new byte[]{'W', 'E', 'B', 'P'};//webp文件头文件标识符
private final static Executor THUMB_CACHE_EXECUTOR = new PriorityExecutor(1, true);//缩略图处理器
private final static LruDiskCache THUMB_CACHE = LruDiskCache.getDiskCache("xUtils_img_thumb");//缩略图硬件缓存
static {
int cpuCount = Runtime.getRuntime().availableProcessors();
BITMAP_DECODE_MAX_WORKER = cpuCount > 4 ? 2 : 1;
}
private ImageDecoder() {
}
/*package*/
static void clearCacheFiles() {
THUMB_CACHE.clearCacheFiles();
}
/**
* decode image file for ImageLoader
*
* @param file
* @param options
* @param cancelable
* @return
* @throws IOException
*/
/*package*/
static Drawable decodeFileWithLock(final File file,
final ImageOptions options,
final Callback.Cancelable cancelable) throws IOException {
if (file == null || !file.exists() || file.length() < 1) return null;
if (cancelable != null && cancelable.isCancelled()) {
throw new Callback.CancelledException("cancelled during decode image");
}
Drawable result = null;
if (!options.isIgnoreGif() && isGif(file)) {
Movie movie = null;
synchronized (gifDecodeLock) { // decode with lock
movie = decodeGif(file, options, cancelable);
}
if (movie != null) {
result = new GifDrawable(movie, (int) file.length());
}
} else {
Bitmap bitmap = null;
{ // decode with lock
try {
while (bitmapDecodeWorker.get() >= BITMAP_DECODE_MAX_WORKER
&& (cancelable == null || !cancelable.isCancelled())) {//获取处理器资源
synchronized (bitmapDecodeLock) {
try {
bitmapDecodeLock.wait();
} catch (InterruptedException iex) {
throw new Callback.CancelledException("cancelled during decode image");
} catch (Throwable ignored) {
}
}
}
if (cancelable != null && cancelable.isCancelled()) {
throw new Callback.CancelledException("cancelled during decode image");
}
bitmapDecodeWorker.incrementAndGet();
// get from thumb cache
if (options.isCompress()) {//尝试从缓存中获取
bitmap = getThumbCache(file, options);
}
if (bitmap == null) {
bitmap = decodeBitmap(file, options, cancelable);//具体的解析图片工作
// save to thumb cache
if (bitmap != null && options.isCompress()) {//存储到缓存
final Bitmap finalBitmap = bitmap;
THUMB_CACHE_EXECUTOR.execute(new Runnable() {
@Override
public void run() {
saveThumbCache(file, options, finalBitmap);
}
});
}
}
} finally {
bitmapDecodeWorker.decrementAndGet();
synchronized (bitmapDecodeLock) {
bitmapDecodeLock.notifyAll();
}
}
}
if (bitmap != null) {
result = new ReusableBitmapDrawable(x.app().getResources(), bitmap);
}
}
return result;
}
public static boolean isGif(File file) {//判断是否为gif
FileInputStream in = null;
try {
in = new FileInputStream(file);
byte[] header = IOUtil.readBytes(in, 0, 3);
return Arrays.equals(GIF_HEADER, header);
} catch (Throwable ex) {
LogUtil.e(ex.getMessage(), ex);
} finally {
IOUtil.closeQuietly(in);
}
return false;
}
public static boolean isWebP(File file) {//判断是否为webp
FileInputStream in = null;
try {
in = new FileInputStream(file);
byte[] header = IOUtil.readBytes(in, 8, 4);
return Arrays.equals(WEBP_HEADER, header);
} catch (Throwable ex) {
LogUtil.e(ex.getMessage(), ex);
} finally {
IOUtil.closeQuietly(in);
}
return false;
}
/**
* 转化文件为Bitmap, 更好的支持WEBP.
*
* @param file
* @param options
* @param cancelable
* @return
* @throws IOException
*/
public static Bitmap decodeBitmap(File file, ImageOptions options, Callback.Cancelable cancelable) throws IOException {//最核心的解析图片操作
{// check params
if (file == null || !file.exists() || file.length() < 1) return null;
if (options == null) {
options = ImageOptions.DEFAULT;
}
if (options.getMaxWidth() <= 0 || options.getMaxHeight() <= 0) {
options.optimizeMaxSize(null);
}
}
Bitmap result = null;
try {
if (cancelable != null && cancelable.isCancelled()) {
throw new Callback.CancelledException("cancelled during decode image");
}
// prepare bitmap options
final BitmapFactory.Options bitmapOps = new BitmapFactory.Options();
bitmapOps.inJustDecodeBounds = true;
bitmapOps.inPurgeable = true;
bitmapOps.inInputShareable = true;
BitmapFactory.decodeFile(file.getAbsolutePath(), bitmapOps);//第一遍解析获取图片大小
bitmapOps.inJustDecodeBounds = false;
bitmapOps.inPreferredConfig = options.getConfig();
int rotateAngle = 0;
int rawWidth = bitmapOps.outWidth;
int rawHeight = bitmapOps.outHeight;
int optionWith = options.getWidth();
int optionHeight = options.getHeight();
if (options.isAutoRotate()) {
rotateAngle = getRotateAngle(file.getAbsolutePath());
if ((rotateAngle / 90) % 2 == 1) {
rawWidth = bitmapOps.outHeight;
rawHeight = bitmapOps.outWidth;
}
}
if (!options.isCrop() && optionWith > 0 && optionHeight > 0) {
if ((rotateAngle / 90) % 2 == 1) {
bitmapOps.outWidth = optionHeight;
bitmapOps.outHeight = optionWith;
} else {
bitmapOps.outWidth = optionWith;
bitmapOps.outHeight = optionHeight;
}
}
bitmapOps.inSampleSize = calculateSampleSize(
rawWidth, rawHeight,
options.getMaxWidth(), options.getMaxHeight());
if (cancelable != null && cancelable.isCancelled()) {
throw new Callback.CancelledException("cancelled during decode image");
}
// decode file
Bitmap bitmap = null;
if (isWebP(file)) {
bitmap = WebPFactory.decodeFile(file.getAbsolutePath(), bitmapOps);//解析具体内容
}
if (bitmap == null) {
bitmap = BitmapFactory.decodeFile(file.getAbsolutePath(), bitmapOps);//第二遍解析具体内容
}
if (bitmap == null) {
throw new IOException("decode image error");
}
{ // 旋转和缩放处理
if (cancelable != null && cancelable.isCancelled()) {
throw new Callback.CancelledException("cancelled during decode image");
}
if (rotateAngle != 0) {
bitmap = rotate(bitmap, rotateAngle, true);
}
if (cancelable != null && cancelable.isCancelled()) {
throw new Callback.CancelledException("cancelled during decode image");
}
if (options.isCrop() && optionWith > 0 && optionHeight > 0) {
bitmap = cut2ScaleSize(bitmap, optionWith, optionHeight, true);
}
}
if (bitmap == null) {
throw new IOException("decode image error");
}
{ // 圆角和方块处理
if (cancelable != null && cancelable.isCancelled()) {
throw new Callback.CancelledException("cancelled during decode image");
}
if (options.isCircular()) {
bitmap = cut2Circular(bitmap, true);
} else if (options.getRadius() > 0) {
bitmap = cut2RoundCorner(bitmap, options.getRadius(), options.isSquare(), true);
} else if (options.isSquare()) {
bitmap = cut2Square(bitmap, true);
}
}
if (bitmap == null) {
throw new IOException("decode image error");
}
result = bitmap;
} catch (IOException ex) {
throw ex;
} catch (Throwable ex) {
LogUtil.e(ex.getMessage(), ex);
result = null;
}
return result;
}
/**
* 转换文件为Movie, 可用于创建GifDrawable.
*
* @param file
* @param options
* @param cancelable
* @return
* @throws IOException
*/
public static Movie decodeGif(File file, ImageOptions options, Callback.Cancelable cancelable) throws IOException {
{// check params
if (file == null || !file.exists() || file.length() < 1) return null;
/*if (options == null) {
options = ImageOptions.DEFAULT; // not use
}
if (options.getMaxWidth() <= 0 || options.getMaxHeight() <= 0) {
options.optimizeMaxSize(null);
}*/
}
InputStream in = null;
try {
if (cancelable != null && cancelable.isCancelled()) {
throw new Callback.CancelledException("cancelled during decode image");
}
int buffSize = 1024 * 16;
in = new BufferedInputStream(new FileInputStream(file), buffSize);
in.mark(buffSize);
Movie movie = Movie.decodeStream(in);
if (movie == null) {
throw new IOException("decode image error");
}
return movie;
} catch (IOException ex) {
throw ex;
} catch (Throwable ex) {
LogUtil.e(ex.getMessage(), ex);
return null;
} finally {
IOUtil.closeQuietly(in);
}
}
/**
* 计算压缩采样倍数
*
* @param rawWidth
* @param rawHeight
* @param maxWidth
* @param maxHeight
* @return
*/
public static int calculateSampleSize(final int rawWidth, final int rawHeight,
final int maxWidth, final int maxHeight) {
int sampleSize = 1;
if (rawWidth > maxWidth || rawHeight > maxHeight) {
if (rawWidth > rawHeight) {
sampleSize = Math.round((float) rawHeight / (float) maxHeight);
} else {
sampleSize = Math.round((float) rawWidth / (float) maxWidth);
}
if (sampleSize < 1) {
sampleSize = 1;
}
final float totalPixels = rawWidth * rawHeight;
final float maxTotalPixels = maxWidth * maxHeight * 2;
while (totalPixels / (sampleSize * sampleSize) > maxTotalPixels) {
sampleSize++;
}
}
return sampleSize;
}
/**
* 裁剪方形图片
*
* @param source
* @param recycleSource 裁剪成功后销毁原图
* @return
*/
public static Bitmap cut2Square(Bitmap source, boolean recycleSource) {
int width = source.getWidth();
int height = source.getHeight();
if (width == height) {
return source;
}
int squareWith = Math.min(width, height);
Bitmap result = Bitmap.createBitmap(source, (width - squareWith) / 2,
(height - squareWith) / 2, squareWith, squareWith);
if (result != null) {
if (recycleSource && result != source) {
source.recycle();
source = null;
}
} else {
result = source;
}
return result;
}
/**
* 裁剪圆形图片
*
* @param source
* @param recycleSource 裁剪成功后销毁原图
* @return
*/
public static Bitmap cut2Circular(Bitmap source, boolean recycleSource) {
int width = source.getWidth();
int height = source.getHeight();
int diameter = Math.min(width, height);
Paint paint = new Paint();
paint.setAntiAlias(true);
Bitmap result = Bitmap.createBitmap(diameter, diameter, Bitmap.Config.ARGB_8888);
if (result != null) {
Canvas canvas = new Canvas(result);
canvas.drawCircle(diameter / 2, diameter / 2, diameter / 2, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(source, (diameter - width) / 2, (diameter - height) / 2, paint);
if (recycleSource) {
source.recycle();
source = null;
}
} else {
result = source;
}
return result;
}
/**
* 裁剪圆角
*
* @param source
* @param radius
* @param isSquare
* @param recycleSource 裁剪成功后销毁原图
* @return
*/
public static Bitmap cut2RoundCorner(Bitmap source, int radius, boolean isSquare, boolean recycleSource) {
if (radius <= 0) return source;
int sourceWidth = source.getWidth();
int sourceHeight = source.getHeight();
int targetWidth = sourceWidth;
int targetHeight = sourceHeight;
if (isSquare) {
targetWidth = targetHeight = Math.min(sourceWidth, sourceHeight);
}
Paint paint = new Paint();
paint.setAntiAlias(true);
Bitmap result = Bitmap.createBitmap(targetWidth, targetHeight, Bitmap.Config.ARGB_8888);
if (result != null) {
Canvas canvas = new Canvas(result);
RectF rect = new RectF(0, 0, targetWidth, targetHeight);
canvas.drawRoundRect(rect, radius, radius, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(source,
(targetWidth - sourceWidth) / 2, (targetHeight - sourceHeight) / 2, paint);
if (recycleSource) {
source.recycle();
source = null;
}
} else {
result = source;
}
return result;
}
/**
* 裁剪并缩放至指定大小
*
* @param source
* @param dstWidth
* @param dstHeight
* @param recycleSource 裁剪成功后销毁原图
* @return
*/
public static Bitmap cut2ScaleSize(Bitmap source, int dstWidth, int dstHeight, boolean recycleSource) {
final int width = source.getWidth();
final int height = source.getHeight();
if (width == dstWidth && height == dstHeight) {
return source;
}
// scale
Matrix m = new Matrix();
int l = 0, t = 0, r = width, b = height;
{
float sx = dstWidth / (float) width;
float sy = dstHeight / (float) height;
if (sx > sy) {
sy = sx;
l = 0;
r = width;
t = (int) ((height - dstHeight / sx) / 2);
b = (int) ((height + dstHeight / sx) / 2);
} else {
sx = sy;
l = (int) ((width - dstWidth / sx) / 2);
r = (int) ((width + dstWidth / sx) / 2);
t = 0;
b = height;
}
m.setScale(sx, sy);
}
Bitmap result = Bitmap.createBitmap(source, l, t, r - l, b - t, m, true);
if (result != null) {
if (recycleSource && result != source) {
source.recycle();
source = null;
}
} else {
result = source;
}
return result;
}
/**
* 旋转图片
*
* @param source
* @param angle
* @param recycleSource
* @return
*/
public static Bitmap rotate(Bitmap source, int angle, boolean recycleSource) {
Bitmap result = null;
if (angle != 0) {
Matrix m = new Matrix();
m.setRotate(angle);
try {
result = Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), m, true);
} catch (Throwable ex) {
LogUtil.e(ex.getMessage(), ex);
}
}
if (result != null) {
if (recycleSource && result != source) {
source.recycle();
source = null;
}
} else {
result = source;
}
return result;
}
/**
* 获取图片旋转角度
*
* @param filePath
* @return
*/
public static int getRotateAngle(String filePath) {
int angle = 0;
try {
ExifInterface exif = new ExifInterface(filePath);
int orientation = exif.getAttributeInt(
ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_UNDEFINED);
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
angle = 90;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
angle = 180;
break;
case ExifInterface.ORIENTATION_ROTATE_270:
angle = 270;
break;
default:
angle = 0;
break;
}
} catch (Throwable ex) {
LogUtil.e(ex.getMessage(), ex);
}
return angle;
}
/**
* 压缩bitmap, 更好的支持webp.
*
* @param bitmap
* @param format
* @param quality
* @param out
* @throws IOException
*/
public static void compress(Bitmap bitmap, Bitmap.CompressFormat format, int quality, OutputStream out) throws IOException {
if (format == Bitmap.CompressFormat.WEBP) {
byte[] data = WebPFactory.encodeBitmap(bitmap, quality);
out.write(data);
} else {
bitmap.compress(format, quality, out);
}
}
/**
* 根据文件的修改时间和图片的属性保存缩略图
*
* @param file
* @param options
* @param thumbBitmap
*/
private static void saveThumbCache(File file, ImageOptions options, Bitmap thumbBitmap) {
if (!WebPFactory.available()) return;
DiskCacheEntity entity = new DiskCacheEntity();
entity.setKey(
file.getAbsolutePath() + "@" + file.lastModified() + options.toString());
DiskCacheFile cacheFile = null;
OutputStream out = null;
try {
cacheFile = THUMB_CACHE.createDiskCacheFile(entity);
if (cacheFile != null) {
out = new FileOutputStream(cacheFile);
byte[] encoded = WebPFactory.encodeBitmap(thumbBitmap, 80);
out.write(encoded);
out.flush();
cacheFile = cacheFile.commit();
}
} catch (Throwable ex) {
IOUtil.deleteFileOrDir(cacheFile);
LogUtil.w(ex.getMessage(), ex);
} finally {
IOUtil.closeQuietly(cacheFile);
IOUtil.closeQuietly(out);
}
}
/**
* 根据文件的修改时间和图片的属性获取缩略图
*
* @param file
* @param options
* @return
*/
private static Bitmap getThumbCache(File file, ImageOptions options) {
if (!WebPFactory.available()) return null;
DiskCacheFile cacheFile = null;
try {
cacheFile = THUMB_CACHE.getDiskCacheFile(
file.getAbsolutePath() + "@" + file.lastModified() + options.toString());
if (cacheFile != null && cacheFile.exists()) {
BitmapFactory.Options bitmapOps = new BitmapFactory.Options();
bitmapOps.inJustDecodeBounds = false;
bitmapOps.inPurgeable = true;
bitmapOps.inInputShareable = true;
bitmapOps.inPreferredConfig = Bitmap.Config.ARGB_8888;
return WebPFactory.decodeFile(cacheFile.getAbsolutePath(), bitmapOps);
}
} catch (Throwable ex) {
LogUtil.w(ex.getMessage(), ex);
} finally {
IOUtil.closeQuietly(cacheFile);
}
return null;
}
}