/*
* Copyright (C) 2016 Peng fei Pan <sky@xiaopan.me>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.xiaopan.sketch.feature.large;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.text.TextUtils;
import java.util.List;
import me.xiaopan.sketch.SLogType;
import me.xiaopan.sketch.Sketch;
import me.xiaopan.sketch.SLog;
import me.xiaopan.sketch.cache.BitmapPoolUtils;
import me.xiaopan.sketch.decode.ImageType;
import me.xiaopan.sketch.util.SketchUtils;
/**
* 大图片查看器
*/
// TODO: 2017/5/8 重新规划设计大图查看器的实现,感觉现在的有些乱(初始化,解码,显示分离)
public class LargeImageViewer {
private static final String NAME = "LargeImageViewer";
private Context context;
private Callback callback;
private TileExecutor tileExecutor;
private TileDecoder tileDecoder;
private TileManager tileManager;
private boolean showTileRect;
private float zoomScale;
private float lastZoomScale;
private Paint drawTilePaint;
private Paint drawTileRectPaint;
private Paint drawLoadingTileRectPaint;
private Matrix matrix;
private boolean running;
private boolean paused;
private String imageUri;
public LargeImageViewer(Context context, Callback callback) {
context = context.getApplicationContext();
this.context = context;
this.callback = callback;
this.tileExecutor = new TileExecutor(new ExecutorCallback());
this.tileManager = new TileManager(context, this);
this.tileDecoder = new TileDecoder(this);
this.matrix = new Matrix();
this.drawTilePaint = new Paint();
}
void draw(Canvas canvas) {
if (tileManager.tileList != null && tileManager.tileList.size() > 0) {
int saveCount = canvas.save();
canvas.concat(matrix);
for (Tile tile : tileManager.tileList) {
if (!tile.isEmpty()) {
canvas.drawBitmap(tile.bitmap, tile.bitmapDrawSrcRect, tile.drawRect, drawTilePaint);
if (showTileRect) {
if (drawTileRectPaint == null) {
drawTileRectPaint = new Paint();
drawTileRectPaint.setColor(Color.parseColor("#88FF0000"));
}
canvas.drawRect(tile.drawRect, drawTileRectPaint);
}
} else if (!tile.isDecodeParamEmpty()) {
if (showTileRect) {
if (drawLoadingTileRectPaint == null) {
drawLoadingTileRectPaint = new Paint();
drawLoadingTileRectPaint.setColor(Color.parseColor("#880000FF"));
}
canvas.drawRect(tile.drawRect, drawLoadingTileRectPaint);
}
}
}
canvas.restoreToCount(saveCount);
}
}
/**
* 设置新的图片
*/
void setImage(String imageUri, boolean correctImageOrientation) {
clean("setImage");
this.imageUri = imageUri;
this.running = !TextUtils.isEmpty(imageUri);
this.tileDecoder.setImage(imageUri, correctImageOrientation);
}
/**
* 更新
*/
void update(Matrix drawMatrix, Rect newVisibleRect, Point previewDrawableSize, Point imageViewSize, boolean zooming) {
// 没有准备好就不往下走了
if (!isReady()) {
SLog.w(SLogType.LARGE, NAME, "not ready. %s", imageUri);
return;
}
// 暂停中也不走了
if (paused) {
SLog.w(SLogType.LARGE, NAME, "not resuming. %s", imageUri);
return;
}
// 传进来的参数不能用就什么也不显示
if (newVisibleRect.isEmpty() || previewDrawableSize.x == 0 || previewDrawableSize.y == 0 || imageViewSize.x == 0 || imageViewSize.y == 0) {
SLog.w(SLogType.LARGE, NAME, "update params is empty. update. newVisibleRect=%s, previewDrawableSize=%dx%d, imageViewSize=%dx%d. %s",
newVisibleRect.toShortString(), previewDrawableSize.x, previewDrawableSize.y, imageViewSize.x, imageViewSize.y, imageUri);
clean("update param is empty");
return;
}
// 如果当前完整显示预览图的话就清空什么也不显示
if (newVisibleRect.width() == previewDrawableSize.x && newVisibleRect.height() == previewDrawableSize.y) {
SLog.d(SLogType.LARGE, NAME, "full display. update. newVisibleRect=%s. %s",
newVisibleRect.toShortString(), imageUri);
clean("full display");
return;
}
// 更新Matrix
lastZoomScale = zoomScale;
matrix.set(drawMatrix);
zoomScale = SketchUtils.formatFloat(SketchUtils.getMatrixScale(matrix), 2);
callback.invalidate();
tileManager.update(newVisibleRect, previewDrawableSize, imageViewSize, getImageSize(), zooming);
}
/**
* 清理资源(不影响继续使用)
*/
private void clean(String why) {
tileExecutor.cleanDecode(why);
matrix.reset();
lastZoomScale = 0;
zoomScale = 0;
tileManager.clean(why);
callback.invalidate();
}
/**
* 回收资源(回收后需要重新setImage()才能使用)
*/
void recycle(String why) {
running = false;
clean(why);
tileExecutor.recycle(why);
tileManager.recycle(why);
tileDecoder.recycle(why);
}
void invalidateView() {
callback.invalidate();
}
TileDecoder getTileDecoder() {
return tileDecoder;
}
TileExecutor getTileExecutor() {
return tileExecutor;
}
/**
* 暂停
*/
public void setPause(boolean pause) {
if (pause == paused) {
return;
}
paused = pause;
if (paused) {
SLog.w(SLogType.LARGE, NAME, "pause. %s", imageUri);
if (running) {
clean("pause");
}
} else {
SLog.i(SLogType.LARGE, NAME, "resume. %s", imageUri);
if (running) {
callback.updateMatrix();
}
}
}
@SuppressWarnings("unused")
public boolean isPaused() {
return paused;
}
/**
* 工作中?
*/
@SuppressWarnings("unused")
public boolean isWorking() {
return !TextUtils.isEmpty(imageUri);
}
/**
* 准备好了?
*/
public boolean isReady() {
return running && tileDecoder.isReady();
}
/**
* 初始化中?
*/
public boolean isInitializing() {
return running && tileDecoder.isInitializing();
}
/**
* 是否显示碎片的范围(红色表示已加载,蓝色表示正在加载)
*/
public boolean isShowTileRect() {
return showTileRect;
}
/**
* 设置是否显示碎片的范围(红色表示已加载,蓝色表示正在加载)
*/
@SuppressWarnings("unused")
public void setShowTileRect(boolean showTileRect) {
this.showTileRect = showTileRect;
callback.invalidate();
}
/**
* 获取当前缩放比例
*/
public float getZoomScale() {
return zoomScale;
}
/**
* 获取上次的缩放比例
*/
public float getLastZoomScale() {
return lastZoomScale;
}
/**
* 获取图片的尺寸
*/
public Point getImageSize() {
return tileDecoder.isReady() ? tileDecoder.getDecoder().getImageSize() : null;
}
/**
* 获取图片的类型
*/
@SuppressWarnings("unused")
public ImageType getImageType() {
return tileDecoder.isReady() ? tileDecoder.getDecoder().getImageType() : null;
}
/**
* 获取图片URI
*/
public String getImageUri() {
return imageUri;
}
/**
* 获取绘制区域
*/
@SuppressWarnings("unused")
public Rect getDrawRect() {
return tileManager.drawRect;
}
/**
* 获取绘制区域在原图中对应的位置
*/
public Rect getDrawSrcRect() {
return tileManager.drawSrcRect;
}
/**
* 获取解码区域
*/
public Rect getDecodeRect() {
return tileManager.decodeRect;
}
/**
* 获取解码区域在原图中对应的位置
*/
public Rect getDecodeSrcRect() {
return tileManager.decodeSrcRect;
}
/**
* 获取碎片列表
*/
public List<Tile> getTileList() {
return tileManager.tileList;
}
/**
* 获取碎片基数,例如碎片基数是3时,就将绘制区域分割成一个(3+1)x(3+1)=16个方块
*/
public int getTiles() {
return tileManager.tiles;
}
/**
* 获取碎片变化监听器
*/
@SuppressWarnings("unused")
public OnTileChangedListener getOnTileChangedListener() {
return tileManager.onTileChangedListener;
}
/**
* 获取碎片变化监听器
*/
public void setOnTileChangedListener(LargeImageViewer.OnTileChangedListener onTileChangedListener) {
tileManager.onTileChangedListener = onTileChangedListener;
}
/**
* 获取碎片占用的内存,单位字节
*/
@SuppressWarnings("unused")
public long getTilesAllocationByteCount() {
if (tileManager.tileList == null || tileManager.tileList.size() <= 0) {
return 0;
}
long bytes = 0;
for (Tile tile : tileManager.tileList) {
if (!tile.isEmpty()) {
bytes += SketchUtils.getByteCount(tile.bitmap);
}
}
return bytes;
}
public interface Callback {
void invalidate();
void updateMatrix();
}
public interface OnTileChangedListener {
void onTileChanged(LargeImageViewer largeImageViewer);
}
private class ExecutorCallback implements TileExecutor.Callback {
@Override
public Context getContext() {
return context;
}
@Override
public void onInitCompleted(String imageUri, ImageRegionDecoder decoder) {
if (!running) {
SLog.w(SLogType.LARGE, NAME, "stop running. initCompleted. %s", imageUri);
return;
}
tileDecoder.initCompleted(imageUri, decoder);
callback.updateMatrix();
}
@Override
public void onInitError(String imageUri, Exception e) {
if (!running) {
SLog.w(SLogType.LARGE, NAME, "stop running. initError. %s", imageUri);
return;
}
tileDecoder.initError(imageUri, e);
}
@Override
public void onDecodeCompleted(Tile tile, Bitmap bitmap, int useTime) {
if (!running) {
SLog.w(SLogType.LARGE, NAME, "stop running. decodeCompleted. tile=%s", tile.getInfo());
BitmapPoolUtils.freeBitmapToPoolForRegionDecoder(bitmap, Sketch.with(context).getConfiguration().getBitmapPool());
return;
}
tileManager.decodeCompleted(tile, bitmap, useTime);
}
@Override
public void onDecodeError(Tile tile, DecodeHandler.DecodeErrorException exception) {
if (!running) {
SLog.w(SLogType.LARGE, NAME, "stop running. decodeError. tile=%s", tile.getInfo());
return;
}
tileManager.decodeError(tile, exception);
}
}
}