图片相关

图片压缩后再上传

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
private void uploadHeadPortrait(Uri imgUri) {
compressPic(imgUri);
}

//压缩图片
private void compressPic(Uri uri){
ImageCompress compress = new ImageCompress();
ImageCompress.CompressOptions options = new ImageCompress.CompressOptions();
// options.uri = Uri.fromFile(new File(sourcePath));
options.uri = uri;
options.maxWidth=Constants.RESIZEBITMAP_WIDTH;
options.maxHeight=Constants.RESIZEBITMAP_HEIGHT;
Bitmap bitmap = compress.compressFromUri(ManagerJJActivity.this, options);
saveCompressedPic(bitmap, uri);
}

//bitmap图片保存到手机指定路径
private void saveCompressedPic(Bitmap bitmap, Uri uri){
boolean isSaved = BitmapUtils.setBitmapToFile(bitmap, Constants.COMPRESSED_PIC_PATH);
if (isSaved){//保存成功用压缩后的图片进行上传
Uri uri1 = Uri.fromFile(new File(Environment.getExternalStorageDirectory(), "compress.jpg"));
uploadPic(uri1);
}else {//未保存成功,用原图片进行上传
uploadPic(uri);
}
}

//从指定位置捞出来图片进行上传
private void uploadPic(Uri imgUri){
if (null == loadingBar) {
loadingBar = LoadingBar.make(findViewById(R.id.activity_manager_jj_rootlayout), new CustomLoadingFactory());
}
if (null != loadingBar) {
loadingBar.show();
loadingBar.setOnClickListener(null);
}

final String filepath;
if (imgUri.toString().startsWith("file")) {
filepath = imgUri.getPath();
} else {
filepath = UiHelper.getFilePathFromContentUri(imgUri, getContentResolver());
}

//采用okhttp3来进行网络请求
String url = UrlManager.URL_IMAGEUPLOAD;
OkHttpClient mOkHttpClient = new OkHttpClient.Builder()
.connectTimeout(Constants.OKHTTPS_CONNECT_TIMEOUT, TimeUnit.MILLISECONDS)
.readTimeout(Constants.OKHTTPS_READ_TIMEOUT, TimeUnit.MILLISECONDS)
.writeTimeout(Constants.OKHTTPS_WRITE_TIMEOUT, TimeUnit.MILLISECONDS)
.build();

RequestBody formBody = new MultipartBody.Builder().setType(MultipartBody.FORM)
// .addFormDataPart("title","Square Logo")
.addFormDataPart("file", "logo-square.png", RequestBody.create(MEDIA_TYPE_PNG, new File(filepath))).build();
Request request = new Request.Builder().url(url).post(formBody).build();
Call call = mOkHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
dialogCancel();

Message msg = new Message();
msg.what = INFO_WHAT;
msg.obj = Constants.UPLOAD_FAIL;
myHandler.sendMessage(msg);
}

@Override
public void onResponse(Call call, Response response) throws IOException {
dialogCancel();
String str = response.body().string();
Log.i(Constants.TAG, str);

JSONObject jsonObject = JSONObject.parseObject(str);
filePath = (String) jsonObject.get("data");
runOnUiThread(new Runnable() {
@Override
public void run() {
Message msg = new Message();
msg.what = INFO_WHAT;
msg.obj = Constants.UPLOAD_SUCCES;
myHandler.sendMessage(msg);
}
});
}

});
}

上面用到底下的图片压缩Bitmap处理:变圆、读取、存、缩放等

ImageCompress图片压缩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
package rongshanghui.tastebychance.com.rongshanghui.util;

import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.provider.MediaStore;
import android.util.Log;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStream;

/**图片压缩工具类
* Created by shenbinghong on 2018/1/12.
*/

public class ImageCompress {
public static final String CONTENT = "content";
public static final String FILE = "file";

/**
* 图片压缩参数
*
* @author Administrator
*
*/
public static class CompressOptions {
public static final int DEFAULT_WIDTH = 400;
public static final int DEFAULT_HEIGHT = 800;

public int maxWidth = DEFAULT_WIDTH;
public int maxHeight = DEFAULT_HEIGHT;
/**
* 压缩后图片保存的文件
*/
public File destFile;
/**
* 图片压缩格式,默认为jpg格式
*/
public Bitmap.CompressFormat imgFormat = Bitmap.CompressFormat.JPEG;

/**
* 图片压缩比例 默认为30
*/
public int quality = 30;

public Uri uri;
}

public Bitmap compressFromUri(Context context,
CompressOptions compressOptions) {

// uri指向的文件路径
String filePath = getFilePath(context, compressOptions.uri);

if (null == filePath) {
return null;
}

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;

Bitmap temp = BitmapFactory.decodeFile(filePath, options);

int actualWidth = options.outWidth;
int actualHeight = options.outHeight;

int desiredWidth = getResizedDimension(compressOptions.maxWidth,
compressOptions.maxHeight, actualWidth, actualHeight);
int desiredHeight = getResizedDimension(compressOptions.maxHeight,
compressOptions.maxWidth, actualHeight, actualWidth);

options.inJustDecodeBounds = false;
options.inSampleSize = findBestSampleSize(actualWidth, actualHeight,
desiredWidth, desiredHeight);

Bitmap bitmap = null;

Bitmap destBitmap = BitmapFactory.decodeFile(filePath, options);

// If necessary, scale down to the maximal acceptable size.
if (destBitmap.getWidth() > desiredWidth
|| destBitmap.getHeight() > desiredHeight) {
bitmap = Bitmap.createScaledBitmap(destBitmap, desiredWidth,
desiredHeight, true);
destBitmap.recycle();
} else {
bitmap = destBitmap;
}

// compress file if need
if (null != compressOptions.destFile) {
compressFile(compressOptions, bitmap);
}

return bitmap;
}

/**
* compress file from bitmap with compressOptions
*
* @param compressOptions
* @param bitmap
*/
private void compressFile(CompressOptions compressOptions, Bitmap bitmap) {
OutputStream stream = null;
try {
stream = new FileOutputStream(compressOptions.destFile);
} catch (FileNotFoundException e) {
Log.e("ImageCompress", e.getMessage());
}

bitmap.compress(compressOptions.imgFormat, compressOptions.quality,
stream);
}

private static int findBestSampleSize(int actualWidth, int actualHeight,
int desiredWidth, int desiredHeight) {
double wr = (double) actualWidth / desiredWidth;
double hr = (double) actualHeight / desiredHeight;
double ratio = Math.min(wr, hr);
float n = 1.0f;
while ((n * 2) <= ratio) {
n *= 2;
}

return (int) n;
}

private static int getResizedDimension(int maxPrimary, int maxSecondary,
int actualPrimary, int actualSecondary) {
// If no dominant value at all, just return the actual.
if (maxPrimary == 0 && maxSecondary == 0) {
return actualPrimary;
}

// If primary is unspecified, scale primary to match secondary's scaling
// ratio.
if (maxPrimary == 0) {
double ratio = (double) maxSecondary / (double) actualSecondary;
return (int) (actualPrimary * ratio);
}

if (maxSecondary == 0) {
return maxPrimary;
}

double ratio = (double) actualSecondary / (double) actualPrimary;
int resized = maxPrimary;
if (resized * ratio > maxSecondary) {
resized = (int) (maxSecondary / ratio);
}
return resized;
}

/**
* 获取文件的路径
*
* @param context
* @param uri
* @return
*/
private String getFilePath(Context context, Uri uri) {

String filePath = null;

if (CONTENT.equalsIgnoreCase(uri.getScheme())) {

Cursor cursor = context.getContentResolver().query(uri,
new String[] { MediaStore.Images.Media.DATA }, null, null, null);

if (null == cursor) {
return null;
}

try {
if (cursor.moveToNext()) {
filePath = cursor.getString(cursor
.getColumnIndex(MediaStore.Images.Media.DATA));
}
} finally {
cursor.close();
}
}

// 从文件中选择
if (FILE.equalsIgnoreCase(uri.getScheme())) {
filePath = uri.getPath();
}

return filePath;
}

/**调用工具类压缩图片
*
ImageCompress compress = new ImageCompress();
ImageCompress.CompressOptions options = new ImageCompress.CompressOptions();
options.uri = Uri.fromFile(new File(sourcePath));
options.maxWidth=Constants.RESIZEBITMAP_WIDTH;
options.maxHeight=Constants.RESIZEBITMAP_HEIGHT;
Bitmap bitmap = compress.compressFromUri(UploadWithPhotoBaseActivity.this, options);
*/
}

在前面的GGBA颜色制作特效这篇笔记中,说了图片由像素组成,像素由
色相,饱和度,亮度组成。当图片的像素不变时,把它读取到内存中不是不会节省开销的。

这里有一个压缩质量的方法来压缩图片,要把图片压缩到100k以下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void compressBmpToFile(Bitmap bmp,File file){  
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int options = 80;
bmp.compress(Bitmap.CompressFormat.JPEG, options, baos);
while (baos.toByteArray().length / 1024 > 100) {
baos.reset();
options -= 10;
bmp.compress(Bitmap.CompressFormat.JPEG, options, baos);
}
try {
FileOutputStream fos = new FileOutputStream(file);
fos.write(baos.toByteArray());
fos.flush();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}

如果原来你读取图片时发生内存溢出,然后高兴的去调用这个方法,你会发现,并没什么卵用。
还是照样溢出。
这是为什么呢?这个图片压缩的是质量,图片的长宽没变,然后得像素不变,然后得出不会节省内存的结论。
所以该溢出还是要溢出。
那么是什么让图片质量下降了?官方文档说这是像素组成元素中的亮度被抛弃的缘故。所以质量压缩会让
图变得模糊。

那么怎么才能降低像素呢,那自然是调整长宽。
长宽变小了,像素自然低了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private Bitmap compressImageFromFile(String srcPath) {  
BitmapFactory.Options newOpts = new BitmapFactory.Options();
newOpts.inJustDecodeBounds = true;//只读边,不读内容 ,这个很重要
Bitmap bitmap = BitmapFactory.decodeFile(srcPath, newOpts);

newOpts.inJustDecodeBounds = false;
int w = newOpts.outWidth;
int h = newOpts.outHeight;
float hh = 800f;//
float ww = 480f;//
int be = 1;
if (w > h && w > ww) {
be = (int) (newOpts.outWidth / ww);
} else if (w < h && h > hh) {
be = (int) (newOpts.outHeight / hh);
}
if (be <= 0)
be = 1;
newOpts.inSampleSize = be;//设置采样率

newOpts.inPreferredConfig = Config.ARGB_8888;//该模式是默认的,可不设
newOpts.inPurgeable = true;// 同时设置才会有效
newOpts.inInputShareable = true;//。当系统内存不够时候图片自动被回收

bitmap = BitmapFactory.decodeFile(srcPath, newOpts);

return bitmap;
}

Bitmap处理:变圆、读取、存、缩放等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
package rongshanghui.tastebychance.com.rongshanghui.util;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;
import android.os.Environment;
import android.util.Log;

import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

/**
* Created by shenbinghong on 2018/1/12.
*/
public class BitmapUtils {
public static Bitmap toRectBmp(Bitmap srcBitmap, int width, int height) {
return to(srcBitmap, width, height, 10f, 10f);
}

public static Bitmap toCircleBmp(Bitmap bitmap) {
int width = bitmap.getWidth();
int height = bitmap.getHeight();
float roundPx = width <= height ? width / 2 : height / 2;
int l = width <= height ? width : height;

return to(bitmap, l, l, roundPx, roundPx);
}

private static Bitmap to(final Bitmap srcBitmap, final int retWidth,
final int retHeight, final float rx, final float ry) {
int width = srcBitmap.getWidth();
int height = srcBitmap.getHeight();
final Rect src;
final Rect dst = new Rect(0, 0, retWidth, retHeight);

if (width <= height) {
src = new Rect(0, 0, width, width);
} else {
float clip = (width - height) / 2;
src = new Rect((int) clip, 0, (int) (width - clip), height);
}
Bitmap output = Bitmap.createBitmap(retWidth, retHeight,
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(output);
final Paint paint = new Paint();
final RectF rectF = new RectF(dst);
paint.setAntiAlias(true);
canvas.drawARGB(0, 0, 0, 0);
paint.setColor(0xff424242);
canvas.drawRoundRect(rectF, rx, ry, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(srcBitmap, src, new Rect(0, 0, retWidth, retHeight),
paint);
return output;
}

/** * 从SDCard上读取图片 * @param pathName * @return */
public static Bitmap getBitmapFromSDCard(String pathName) {
return BitmapFactory.decodeFile(pathName);
}

/** * 缩放图片 * @param bitmap * @param width * @param height * @return */
public static Bitmap zoomBitmap(Bitmap bitmap, int width, int height) {
int w = bitmap.getWidth();
int h = bitmap.getHeight();
Matrix matrix = new Matrix();
matrix.postScale((float) width / w, (float) height / h);
return Bitmap.createBitmap(bitmap, 0, 0, w, h, matrix, true);
}

/** * 将Drawable转化为Bitmap * @param drawable * @return */
public static Bitmap drawableToBitmap(Drawable drawable) {
int width = drawable.getIntrinsicWidth();
int height = drawable.getIntrinsicHeight();
Bitmap bitmap = Bitmap.createBitmap(width, height, drawable
.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888
: Bitmap.Config.RGB_565);
Canvas canvas = new Canvas(bitmap);
canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG|Paint.FILTER_BITMAP_FLAG));
drawable.setBounds(0, 0, width, height);
drawable.draw(canvas);
return bitmap;
}

/** * 获得圆角图片 * @param bitmap * @param roundPx * @return */
public static Bitmap getRoundedCornerBitmap(Bitmap bitmap, float roundPx) {
Bitmap output = null;
try {
output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(),
Bitmap.Config.ARGB_8888);
} catch (Exception e) {
// TODO: handle exception
output = Bitmap.createBitmap(bitmap.getWidth() / 3,
bitmap.getHeight() / 3, Bitmap.Config.ARGB_8888);
}
;
Canvas canvas = new Canvas(output);
final Paint paint = new Paint();
final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
final RectF rectF = new RectF(rect);
canvas.drawRoundRect(rectF, roundPx, roundPx, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(bitmap, rect, rect, paint);
return output;
}

/** * 获得带倒影的图片 * @param bitmap * @return */
public static Bitmap getReflectionImageWithOrigin(Bitmap bitmap) {
final int reflectionGap = 4;
int width = bitmap.getWidth();
int height = bitmap.getHeight();
Matrix matrix = new Matrix();
matrix.preScale(1, -1);
Bitmap reflectionImage = Bitmap.createBitmap(bitmap, 0, height / 2,
width, height / 2, matrix, false);
Bitmap bitmapWithReflection = Bitmap.createBitmap(width,
(height + height / 2), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmapWithReflection);
canvas.drawBitmap(bitmap, 0, 0, null);
Paint defaultPaint = new Paint();
canvas.drawRect(0, height, width, height + reflectionGap, defaultPaint);
canvas.drawBitmap(reflectionImage, 0, height + reflectionGap, null);
Paint paint = new Paint();
LinearGradient shader = new LinearGradient(0, bitmap.getHeight(), 0,
bitmapWithReflection.getHeight() + reflectionGap, 0x70ffffff,
0x00ffffff, Shader.TileMode.CLAMP);
paint.setShader(shader);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
canvas.drawRect(0, height, width, bitmapWithReflection.getHeight()
+ reflectionGap, paint);
return bitmapWithReflection;
}

public static byte[] readStream(InputStream inStream) throws Exception {
byte[] buffer = new byte[1024];
int len = -1;
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
while ((len = inStream.read(buffer)) != -1) {
outStream.write(buffer, 0, len);
}
byte[] data = outStream.toByteArray();
outStream.close();
inStream.close();
return data;
}

public static Bitmap getPicFromBytes(byte[] bytes,
BitmapFactory.Options opts) {
if (bytes != null)
if (opts != null)
return BitmapFactory.decodeByteArray(bytes, 0, bytes.length,
opts);
else
return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
return null;
}

public static boolean setBitmapToFile(Bitmap mImg, String path) {
BufferedOutputStream bos = null;
/*File mFile = new File(path);
if (!mFile.exists()){
mFile.mkdirs();
}*/

File mFile = new File(Environment.getExternalStorageDirectory(), "compress.jpg");
if (!mFile.exists()){
try {
mFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}

try {
bos = new BufferedOutputStream(new FileOutputStream(mFile));
if (mImg != null) {
mImg.compress(Bitmap.CompressFormat.JPEG, 100, bos);
}
// finish();
return true;
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
try {
bos.flush();
bos.close();
mFile = null;
} catch (IOException e) {
e.printStackTrace();
}
}

return false;
}

public static Bitmap getWebImage(String url) {
Log.i("returnBitMap", "url=" + url);
URL myFileUrl = null;
Bitmap bitmap = null;
try {
myFileUrl = new URL(url);
} catch (MalformedURLException e) {
e.printStackTrace();
}
try {
HttpURLConnection conn = (HttpURLConnection) myFileUrl
.openConnection();
conn.setDoInput(true);
conn.connect();
InputStream is = conn.getInputStream();
bitmap = BitmapFactory.decodeStream(is);
is.close();
} catch (IOException e) {
e.printStackTrace();
}
return bitmap;
}

// public static void getMediaFile(String url, String fileName)
// throws IOException {
// Log.i("getMediaFile", "url=" + url);
// URL myFileUrl = null;
// InputStream is = null;
// HttpURLConnection conn;
// File file = new File(DataBase.CARD_FILE_PATH + fileName + ".amr");
// if (!file.exists()) {
// file.createNewFile();
// }
// try {
// myFileUrl = new URL(url);
// } catch (MalformedURLException e) {
// e.printStackTrace();
// }
// try {
// conn = (HttpURLConnection) myFileUrl.openConnection();
// conn.setDoInput(true);
// conn.connect();
// is = conn.getInputStream();
// } catch (IOException e) {
// e.printStackTrace();
// }
// OutputStream os = new FileOutputStream(file);
// byte[] buffer = new byte[1024 * 2];
// while ((is.read(buffer)) != -1) {
// os.write(buffer);
// }
// if (null != os) {
// os.flush();
// os.close();
// }
// if (null != is) {
// is.close();
// }
//
// }

/**
* resize
*
* @author c.y
* */
public static Bitmap loadResizedBitmap(String filename, int width,
int height, boolean exact) {
Bitmap bitmap = null;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filename, options);
if (options.outHeight > 0 && options.outWidth > 0) {
options.inJustDecodeBounds = false;
options.inSampleSize = 2;
while (options.outWidth / options.inSampleSize > width
&& options.outHeight / options.inSampleSize > height) {
options.inSampleSize++;
}
bitmap = BitmapFactory.decodeFile(filename, options);
if (bitmap != null && exact) {
bitmap = Bitmap
.createScaledBitmap(bitmap, width, height, false);
}
}
return bitmap;
}

public static byte[] Bitmap2Bytes(Bitmap bm) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.PNG, 100, baos);
return baos.toByteArray();
}
}

自己定义的多种图片缓存(Java六大原则)

自定义图片缓存策略

  • 定义缓存接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface ImageCache {    
/**
* 添加缓存
* @param url 缓存的图片url作为缓存的key
* @param bitmap 缓存的bitmap
*/
void put(String url, Bitmap bitmap);
/**
* 获取缓存的图片
* @param url 缓存的图片url
* @return
*/
Bitmap get(String url);
}
  • 内存缓存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class MemoryCache implements ImageCache {
public MemoryCache() {
initImageCache();
}

LruCache<String, Bitmap> memoryCache;
private void initImageCache() {
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);//计算可使用的最大内存
int cacheSize = maxMemory / 4;//只用最大内存的四分之一作为缓存的大小
memoryCache = new LruCache<String, Bitmap>(cacheSize){
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() / 1024;
}
};
}

@Override
public void put(String url, Bitmap bitmap) {
memoryCache.put(url, bitmap);
}

@Override
public Bitmap get(String url) {
return memoryCache.get(url);
}
}
  • sd卡缓存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class DiskCache implements ImageCache{
public static final String cacheDir = "sdcard/cache/";

@Override
public void put(String url, Bitmap bitmap) {
FileOutputStream outputStream = null;
try {
outputStream = new FileOutputStream(cacheDir + url);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
CloseableUtil.close(outputStream);
}
}

@Override
public Bitmap get(String url) {
return BitmapFactory.decodeFile(cacheDir + url);
}
}
  • 双缓存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class DoubleCache implements ImageCache {
MemoryCache memoryCache = new MemoryCache();//内存缓存
DiskCache diskCache = new DiskCache();//sd卡缓存

@Override
public void put(String url, Bitmap bitmap) {
memoryCache.put(url, bitmap);
diskCache.put(url, bitmap);
}

@Override
public Bitmap get(String url) {
Bitmap bitmap = null;
bitmap = memoryCache.get(url);
if (bitmap == null){
bitmap = diskCache.get(url);
}
return bitmap;
}
}
  • 关闭流的类
1
2
3
4
5
6
7
8
9
10
11
public class CloseableUtil {
public static void close(Closeable closeable){
try {
if (closeable != null){
closeable.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
  • 图片加载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
public class ImageLoader {

public ImageLoader() {
}

//图片缓存类
ImageCache imageCache = new MemoryCache();

//线程池,线程数量为CPU的数量
ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

/**
* 设置要使用哪种缓存
* @param imageCache
*/
public void setImageCache(ImageCache imageCache){
this.imageCache = imageCache;
}

/**
* 显示图片
* @param url 图片的url
* @param imageView 要显示的view
*/
public void displayImage(final String url, final ImageView imageView){
Bitmap bitmap = imageCache.get(url);

if (bitmap != null){
imageView.setImageBitmap(bitmap);
return;
}
imageView.setTag(url);
mExecutorService.submit(new Runnable() {
@Override
public void run() {
Bitmap bitmap = downloadImage(url);
if (bitmap == null){
return;
}

if (imageView.getTag().equals(url)){
imageView.setImageBitmap(bitmap);
}
imageCache.put(url, bitmap);
}
});



}

/**
* 下载图片
* @param imageUrl 网络的图片地址
* @return 返回bitmap对象
*/
private Bitmap downloadImage(String imageUrl) {
Bitmap bitmap = null;
try {
URL url = new URL(imageUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
bitmap = BitmapFactory.decodeStream(conn.getInputStream());
conn.disconnect();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return bitmap;
}
}

调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
ImageLoader imageLoader = new ImageLoader();
/**
* 内存缓存方式
*/
imageLoader.setImageCache(new MemoryCache());
/**
* SD卡缓存方式
*/
imageLoader.setImageCache(new DiskCache());
/**
* 双缓存方式
*/
imageLoader.setImageCache(new DoubleCache());
/**
* 自定义缓存方式
*/
imageLoader.setImageCache(new ImageCache() {
@Override
public void put(String url, Bitmap bitmap) {
//用户自定义的缓存方式
}

@Override
public Bitmap get(String url) {
//从缓存中获取图片
return null;
}
});
imageLoader.displayImage(url, imageView);

ImageIO图片工具类(获取路径、保存相册等)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
package com.u1city.androidframe.common.file.image;

import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.v4.content.FileProvider;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;

import com.u1city.androidframe.common.file.StorageFileManager;
import com.u1city.androidframe.common.text.StringUtils;
import com.u1city.androidframe.framework.model.file.FileOptions;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Calendar;

/**
* 图片存取工具
* Created by zhengjb on 2017/1/13.
*/

public class ImageIO {

private static final String TAG = "ImageIO";

private static final int NO_LIMIT_W = -1;
private static final int NO_LIMIT_H = -1;

/**
* 获取SD卡中最新图片路径
*/
public static String getLatestImage(Context context) {
String latestImage = null;
String[] items = {MediaStore.Images.Media._ID, MediaStore.Images.Media.DATA};
Cursor cursor = context.getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, items, null, null, MediaStore.Images.Media._ID + " desc");

if (cursor != null && cursor.getCount() > 0) {
cursor.moveToFirst();
for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
latestImage = cursor.getString(1);
break;
}
cursor.close();
}

return latestImage;
}

/**
* 从文件中获取图片,调用者可以指定一个期望的宽高以节省所需内存
*
* @param file 目标文件
* @return bitmap
*/
public static Bitmap readImageFromFile(File file, BitmapFactory.Options options, int desireWidth, int desireHeight) {
if (options == null) {
options = new BitmapFactory.Options();
}

options.inJustDecodeBounds = true;
int acWidth, acHeight;
BitmapFactory.decodeFile(file.getAbsolutePath(), options);
acWidth = options.outWidth;
acHeight = options.outHeight;
boolean needScale = true;
boolean fitWidth = false;

//期望的高度,<=0时不做限制
if (desireHeight <= 0) {
fitWidth = true;
}

//期望的宽度,<=0时不做限制
if (desireWidth <= 0) {
fitWidth = false;
}
if (desireHeight <= 0 && desireWidth <= 0) {
needScale = false;
}
if (desireHeight > 0 && desireWidth > 0) {
fitWidth = desireWidth / desireHeight < 1;
}
if (needScale) {
if (fitWidth) {
if (acWidth <= desireWidth) {
needScale = false;
}
} else {
if (acHeight <= desireHeight) {
needScale = false;
}
}
}
if (needScale) {
int scale = 1;
if (fitWidth) {
while (acWidth > desireWidth) {
scale *= 2;
acWidth = acWidth / scale;
}
} else {
while (acHeight > desireHeight) {
scale *= 2;
acHeight = acHeight / scale;
}
}
options.inSampleSize = scale;
}
options.inJustDecodeBounds = false;
return BitmapFactory.decodeFile(file.getAbsolutePath(), options);
}

/**
* 把图片bitmap写到file文件中
*
* @param bitmap 将要写入的文件
* @param file 目标文件
* @return true操作成功,false操作失败
*/
public static boolean writeImage2File(Bitmap bitmap, File file) {
try {
FileOutputStream fos = new FileOutputStream(file);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
byte[] bytes = stream.toByteArray();
fos.write(bytes);
fos.close();
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}

/**
* 保存图图片
*
* @param context 上下文
* @param sour 要保存的图片
* @param fileName 保存文件的名称
* @throws IOException 创建文件失败时抛出“创建文件失败异常”
*/
public static File saveImage(Context context, Bitmap sour, String fileName) throws IOException {
File f = obtainImageFile(context, fileName);
if (f == null) {
throw new IOException("创建文件失败");
}
writeImage2File(sour, f);
return f;
}

/**
* 获得保存图片的文件
*
* @param context 上下文
* @param fileName 文件名称
* @return 返回获得的文件f
*/
public static File obtainImageFile(Context context, String fileName) {
FileOptions op = new FileOptions();
op.setFileType(FileOptions.FILE_TYPE_EXT_IMAGE);
op.setFileName(fileName);
return StorageFileManager.findFile(context, op);
}

/**
* 获得默认文件名的图片文件
*/
public static File obtainImageFile(Context context) {
return obtainImageFile(context, generateDefaultFileName());
}

/**
* 生成默认的文件名称
*/
public static String generateDefaultFileName() {
Calendar c = Calendar.getInstance();
StringBuffer fname;
fname = new StringBuffer();
fname.append(c.get(Calendar.YEAR)).
append(c.get(Calendar.MONTH) + 1).
append(c.get(Calendar.DAY_OF_MONTH)).
append(c.get(Calendar.HOUR_OF_DAY)).
append(c.get(Calendar.MINUTE)).
append(c.get(Calendar.SECOND));
return fname.toString();
}

/**
* 根据uri获取图片的绝对路径
*
* @param context 上下文
* @param uri 目标uri
* @return 图片的绝对路径
*/
public static String getImagePathFromUri(Context context, Uri uri) {
String imagePath = "";
String[] proj = {MediaStore.Images.Media.DATA};
Cursor cursor = null;
try {
cursor = context.getContentResolver().query(uri, proj, // Which columns to
// return
null, // WHERE clause; which rows to return (all rows)
null, // WHERE clause selection arguments (none)
null); // Order-by clause (ascending by name)

if (cursor != null) {
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
if (cursor.getCount() > 0 && cursor.moveToFirst()) {
imagePath = cursor.getString(column_index);
}
}
} finally {
if (cursor != null) {
cursor.close();
}
}
return imagePath;
}

public static Bitmap loadImgThumbnail(Context context, String imgName) {
Bitmap bitmap = null;
Cursor cursor = null;
String[] proj = {MediaStore.Images.Media._ID, MediaStore.Images.Media.DISPLAY_NAME};
try {
cursor = context.getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, proj, MediaStore.Images.Media.DISPLAY_NAME + "='" + imgName + "'", null, null);
if (cursor != null && cursor.getCount() > 0 && cursor.moveToFirst()) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 1;
}
} finally {
cursor.close();
}
return bitmap;
}

/**
* 从sour指定的文件中加载图片
*
* @param context 上下文
* @param sour 目标文件
* @param fitScreen true则图片宽高将适应屏幕宽高,在加载大图时可以节省内存
* @return 从sour加载到的图片
*/
public static Bitmap loadBitmapFromFile(Context context, File sour, boolean fitScreen) {
int dirWidth, dirHeight;
if (fitScreen) {
DisplayMetrics dm = context.getResources().getDisplayMetrics();
dirWidth = dm.widthPixels;
dirHeight = dm.heightPixels;
} else {
dirWidth = NO_LIMIT_W;
dirHeight = NO_LIMIT_H;
}

return readImageFromFile(sour, null, dirWidth, dirHeight);
}

/**
* 把bitmap保存到file指定的文件中
*
* @param file 保存bitmap的文件
* @param bitmap 将要保存的图片
* @return 保存bitmap的文件
*/
public static File saveBitmap(File file, Bitmap bitmap) {
if (file == null) {
Log.w(TAG, "请传入有效的文件");
return null;
}
writeImage2File(bitmap, file);
return file;
}

public static Bitmap readBitmap(File file) {
return readImageFromFile(file, null, -1, -1);
}

/**
* 保存图片至图片库
*/
public static void saveImageToGallery(Context context, Bitmap bmp, String filePackageName, String fileName) {
//当fileName为null时则自动生成文件名
if (StringUtils.isEmpty(fileName)) {
fileName = generateDefaultFileName() + ".jpg";
}
// 首先保存图片
File appDir = new File(Environment.getExternalStorageDirectory(), filePackageName);
if (!appDir.exists()) {
appDir.mkdir();
}
//String fileName = "wxcode.jpg";
File file = new File(appDir, fileName);
try {
saveBitmap(file, bmp);
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
if (file != null) {
Uri uri;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
uri = FileProvider.getUriForFile(context, context.getPackageName() + ".updateProvider", file);
} else {
uri = Uri.fromFile(file);
}
intent.setData(uri);
}
context.sendBroadcast(intent);//这个广播的目的就是更新图库
} catch (Exception e) {
e.printStackTrace();
}
}

public static void saveImageToGallery(Context context, Bitmap sour) {
try {
File f = saveImage(context, sour, generateDefaultFileName());
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
if (f != null) {
Uri uri;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
uri = FileProvider.getUriForFile(context, context.getPackageName() + ".updateProvider", f);
} else {
uri = Uri.fromFile(f);
}
intent.setData(uri);
}
context.sendBroadcast(intent);//这个广播的目的就是更新图库
} catch (IOException e) {
e.printStackTrace();
}
}

/**
* 是否成功保存到系统相册
* @param context
* @param bmp
* @param filePackageName
* @param fileName
* @return
*/
public static boolean isSaveImageToGallerySuc(Context context, Bitmap bmp, String filePackageName, String fileName) {
//当fileName为null时则自动生成文件名
if (StringUtils.isEmpty(fileName)) {
fileName = generateDefaultFileName() + ".jpg";
}
// 首先保存图片
File appDir = new File(Environment.getExternalStorageDirectory(), filePackageName);
if (!appDir.exists()) {
appDir.mkdir();
}
//String fileName = "wxcode.jpg";
File file = new File(appDir, fileName);
try {
saveBitmap(file, bmp);
insertPicToGallery(context, file);
//删除临时保存的图片
deleteImg(context, file);
return true;
} catch (Exception e) {
e.printStackTrace();
}

return false;
}


public static void deleteImg(Context context, File file){
if(!TextUtils.isEmpty(file.getAbsolutePath())){
Uri mImageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
ContentResolver mContentResolver = context.getContentResolver();
String where = MediaStore.Images.Media.DATA + "='" + file.getAbsolutePath() + "'";
//删除图片
mContentResolver.delete(mImageUri, where, null);
}


if (file.exists()) { // 判断文件是否存在
if (file.isFile()) { // 判断是否是文件
file.delete(); // delete()方法 你应该知道 是删除的意思;
} else if (file.isDirectory()) { // 否则如果它是一个目录
File files[] = file.listFiles(); // 声明目录下所有的文件 files[];
for (int i = 0; i < files.length; i++) { // 遍历目录下所有的文件
context.deleteFile(String.valueOf(files[i])); // 把每个文件 用这个方法进行迭代
}
}
file.delete();
} else {
Log.e("删除文件","文件不存在!"+"\n");
}
}

/**
* 已经保存的图片插入到相册内
* @param context
* @param file
*/
public static void insertPicToGallery(Context context, File file){

try {
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
if (file != null) {
Uri uri;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
MediaStore.Images.Media.insertImage(context.getContentResolver(), file.getPath(), file.getName(), "description");
uri = Uri.parse("file://"+ Environment.getExternalStorageDirectory());
} else {
uri = Uri.fromFile(file);
}
intent.setData(uri);
}

context.sendBroadcast(intent);//这个广播的目的就是更新图库
} catch (Exception e) {
e.printStackTrace();
}
}

/**
* Gets the content:// URI from the given corresponding path to a file
* @param context
* @param imageFile
* @return content Uri
*/
public static Uri getImageContentUri(Context context, java.io.File imageFile) {
String filePath = imageFile.getAbsolutePath();
Cursor cursor = context.getContentResolver().query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
new String[] { MediaStore.Images.Media._ID },
MediaStore.Images.Media.DATA + "=? ",
new String[] { filePath }, null);
if (cursor != null && cursor.moveToFirst()) {
int id = cursor.getInt(cursor
.getColumnIndex(MediaStore.MediaColumns._ID));
Uri baseUri = Uri.parse("content://media/external/images/media");
return Uri.withAppendedPath(baseUri, "" + id);
} else {
if (imageFile.exists()) {
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DATA, filePath);
return context.getContentResolver().insert(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
} else {
return null;
}
}
}
}

Pic2Ascii图片转ascii码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package me.veryyoung;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
/**
* Created by veryyoung on 2015/9/28.
* 类功能:将图片转换为 ascii 码
* 使用:运行 Pic2Ascii.java 的 main 方法,并传入图片的有效路径。
*/
public class Pic2Ascii {

/**
* Some empirically chosen characters that give good results.
*/
private static final char[] defaultCharacters = "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\\|()1{}[]?-_+~<>i!lI;:,\"^`'. "
.toCharArray();

public static void main(String[] args) {
if (0 == args.length || args[0].equals("")) {
System.err.println("请输入正确的图片路径");
return;
}
System.out.println(transform(args[0]));

}

public static String transform(String path) {
BufferedImage image = getImage(path);

StringBuffer stringBuffer = new StringBuffer();

for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
int index = (int) (Math.random() * defaultCharacters.length);
stringBuffer.append(isBlack(image.getRGB(x, y)) ? " " : defaultCharacters[index]);
stringBuffer.append(isBlack(image.getRGB(x, y)) ? " " : defaultCharacters[index]);
}
stringBuffer.append("\n\r");
}
return stringBuffer.toString();

}


public static boolean isBlack(int pixel) {
boolean result = false;

int r = (pixel >> 16) & 0xff;
int g = (pixel >> 8) & 0xff;
int b = (pixel) & 0xff;


int tmp = r * r + g * g + b * b;
if (tmp > 3 * 128 * 128) {
result = true;
}

return result;
}

public static BufferedImage getImage(String path) {
BufferedImage image = null;
try {
image = ImageIO.read(new File(path));
} catch (IOException e) {
System.err.println("未能获取到图片");
e.printStackTrace();
}

return image;
}
}

截图工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
package com.u1city.androidframe.common.image.screenshot;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.webkit.WebView;

import static com.u1city.androidframe.common.display.DimensUtil.getDisplayHeight;
import static com.u1city.androidframe.common.display.DimensUtil.getDisplayWidth;

/**屏幕截图
* create by shenbh on 2018/6/19 ,on Mac.
*/
public class ScreenShotUtils {

/**
* 获取当前屏幕截图,包含状态栏
* @param activity
* @return
*/
public static Bitmap snapShotWithStatusBar(Activity activity) {
View view = activity.getWindow().getDecorView();
view.setDrawingCacheEnabled(true);
view.buildDrawingCache();
Bitmap bitmap= view.getDrawingCache();
int width = getDisplayWidth(activity);//获取屏幕的宽
int height = getDisplayHeight(activity);//获取屏幕的高
Bitmap bm = null;
bm = Bitmap.createBitmap(bitmap, 0, 0, width, height);
view.destroyDrawingCache();
return bm;

}

/**
* 获取当前屏幕截图,不包含状态栏
* @param activity
* @return
*/
public static Bitmap snapShotWithoutStatusBar(Activity activity) {
View view = activity.getWindow().getDecorView();
view.setDrawingCacheEnabled(true);
view.buildDrawingCache();
Bitmap bitmap= view.getDrawingCache();
Rect frame = new Rect();
activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
int statusBarHeight = frame.top;//获取状态栏的高
int width = getDisplayWidth(activity);//获取屏幕的宽
int height = getDisplayHeight(activity);//获取屏幕的高
Bitmap bm = Bitmap.createBitmap(bitmap, 0, statusBarHeight, width, height - statusBarHeight);
view.destroyDrawingCache();
return bm;

}

/**
* 获取当前屏幕截图,不包含状态栏以及标题栏(ActionBar)
* @param activity
* @return
*/
public static Bitmap snapShotWithoutStatusBarAndTitle(Activity activity) {
View view = activity.getWindow().getDecorView();
view.setDrawingCacheEnabled(true);
view.buildDrawingCache();
Bitmap bitmap= view.getDrawingCache();
Rect frame = new Rect();
activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
int statusBarHeight = frame.top;//获取状态栏的高
int titleBarHeight = activity.getWindow().findViewById(Window.ID_ANDROID_CONTENT).getTop();//获取标题栏的高
int width = getDisplayWidth(activity);//获取屏幕的宽
int height = getDisplayHeight(activity);//获取屏幕的高
int totalBarHeight=titleBarHeight+statusBarHeight;
Bitmap bm= Bitmap.createBitmap(bitmap, 0, totalBarHeight , width, height - totalBarHeight);
view.destroyDrawingCache();
return bm;
}

/**
* 截取Scrollview等ViewGroup里面的内容,常用于生成长微博,效果可参照简书的生成图片分享
* @param view
* @return
*/
public static Bitmap snapShotForViewGroup(ViewGroup view) {
int totalHeight = 0;
// 获取ViewGroup实际高度
for (int i = 0; i < view.getChildCount(); i++) {
totalHeight += view.getChildAt(i).getHeight();
//view.getChildAt(i).setBackgroundColor(0xffffffff);//这里可以设置背景颜色,以免背景透明看不出效果
}
Bitmap bitmap= Bitmap.createBitmap(view.getWidth(), totalHeight,
Bitmap.Config.RGB_565);
Canvas canvas = new Canvas(bitmap);
view.draw(canvas);
return bitmap;
}


/**截取WebView里面的全面内容
*
* Android 5.0以上 在Activity的setContentView之前要进行如下处理
* if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
* WebView.enableSlowWholeDocumentDraw();
* }
* @param view
* @return
*/
public static Bitmap snapShotForWebView(WebView view) {
float scale = view.getScale(); //获取webview缩放率
int webViewHeight = (int) (view.getContentHeight() * scale);//得到缩放后webview内容的高度
Bitmap bitmap = Bitmap.createBitmap(view.getWidth(),webViewHeight,Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
view.draw(canvas);
return bitmap;
}
}

指定View的截图

截图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.util.LruCache;
import android.view.Display;
import android.view.View;
import android.webkit.WebView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.ScrollView;

import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayList;

/**
* <pre>
* desc : 截图
* author : shenbh
* e-mail : shenbh@qq.com
* time : 2022-02-16 17:37
* </pre>
*/
public class ImageCapture {

/**
* 当前页面截图(截取整个屏幕)
* 截取当前窗体的截图,根据[isShowStatusBar]判断是否包含当前窗体的状态栏
* 原理是获取当前窗体decorView的缓存生成图片
*/
public Bitmap captureWindow(Activity activity, Boolean isShowStatusBar) {
// 获取当前窗体的View对象
View view = activity.getWindow().getDecorView();
view.setDrawingCacheEnabled(true);
// 生成缓存
view.buildDrawingCache();

Bitmap bitmap = null;
if (isShowStatusBar) {
// 绘制整个窗体,包括状态栏
bitmap = Bitmap.createBitmap(view.getDrawingCache(), 0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
} else {
// 获取状态栏高度
Rect rect = new Rect();
view.getWindowVisibleDisplayFrame(rect);
Display display = activity.getWindowManager().getDefaultDisplay();

// 减去状态栏高度
bitmap = Bitmap.createBitmap(view.getDrawingCache(), 0, rect.top, display.getWidth(), display.getHeight() - rect.top);
}

view.setDrawingCacheEnabled(false);
view.destroyDrawingCache();

return bitmap;
}

/**
* 截取常用的View(TextView,RelativeLayout...)
*
* View已经在界面上展示了,可以直接获取View的缓存
* 对View进行量测,布局后生成View的缓存
* View为固定大小的View,包括TextView,ImageView,LinearLayout,FrameLayout,RelativeLayout等
* @param view 截取的View,View必须有固定的大小,不然drawingCache返回null
* @return 生成的Bitmap
*/
public Bitmap captureView(View view) {
view.setDrawingCacheEnabled(true);
view.buildDrawingCache();
// 重新测量一遍View的宽高
view.measure(View.MeasureSpec.makeMeasureSpec(view.getWidth(), View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(view.getHeight(), View.MeasureSpec.EXACTLY));
// 确定View的位置
view.layout((int)view.getX(), (int)view.getY(), (int)view.getX() + view.getMeasuredWidth(),
(int)view.getY() + view.getMeasuredHeight());
// 生成View宽高一样的Bitmap
Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache(), 0, 0, view.getMeasuredWidth(),
view.getMeasuredHeight());
view.setDrawingCacheEnabled(false);
view.destroyDrawingCache();
return bitmap;
}

/**
* 截取ScrollerView
* 原理是获取scrollView的子View的高度,然后创建一个子View宽高的画布,将ScrollView绘制在画布上
* @param scrollView 控件
* @return 返回截图后的Bitmap
*/
public Bitmap captureScrollView(ScrollView scrollView) {
int h = 0;
for (int i = 0; i < scrollView.getChildCount(); i++) {
View childView = scrollView.getChildAt(i);
// 获取子View的高度
h += childView.getHeight();
// 设置背景颜色,避免布局里未设置背景颜色,截的图背景黑色
childView.setBackgroundColor(Color.parseColor("#FFFFFF"));
}

Bitmap bitmap = Bitmap.createBitmap(scrollView.getWidth(), h, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
// 将ScrollView绘制在画布上
scrollView.draw(canvas);
return bitmap;
}

/**
* 截取ListView
* 原理:获取到每一个子View,将子View生成的bitmap存入集合,并且累积ListView高度
* 遍历完成后,创建一个ListView大小的画布,将集合的Bitmap绘制到画布上
* @param listView 截图控件对象
* @return 生成的截图对象
*/
public Bitmap captureListView(ListView listView) {
ListAdapter adapter = listView.getAdapter();
int itemCount = adapter.getCount();
int allitemsheight = 0;
ArrayList<Bitmap> bitmaps = new ArrayList<Bitmap>();

for (int i = 0; i < itemCount; i++) {
// 获取每一个子View
View childView = adapter.getView(i, null, listView);
// 测量宽高
childView.measure(
View.MeasureSpec.makeMeasureSpec(listView.getWidth(), View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));

// 布局位置
childView.layout(0, 0, childView.getMeasuredWidth(), childView.getMeasuredHeight());
// 设置背景颜色,避免是黑色的
childView.setBackgroundColor(Color.parseColor("#FFFFFF"));
childView.setDrawingCacheEnabled(true);
// 生成缓存
childView.buildDrawingCache();
// 将每一个View的截图加入集合
bitmaps.add(childView.getDrawingCache());
// 叠加截图高度
allitemsheight += childView.getMeasuredHeight();
}

// 创建和ListView宽高一样的画布
Bitmap bitmap = Bitmap.createBitmap(listView.getMeasuredWidth(), allitemsheight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);

Paint paint = new Paint();
float iHeight = 0f;

for (int i = 0; i < bitmaps.size(); i++) {
Bitmap bmp = bitmaps.get(i);
// 将每一个生成的bitmap绘制在画布上
canvas.drawBitmap(bmp, 0f, iHeight, paint);
iHeight += bmp.getHeight();

bmp.recycle();
}
return bitmap;
}

/**
* 截取RecyclerView
* 原理和ListView集合是一样的,获取到每一个Holder的截图放入集合,最后统一绘制到Bitmap上
* @param recyclerView&emsp;要截图的控件
* @return 生成的截图
*/
public Bitmap captureRecyclerView(RecyclerView recyclerView) {
RecyclerView.Adapter adapter = recyclerView.getAdapter();
Bitmap bigBitmap = null;
if (adapter != null) {
int size = adapter.getItemCount();
int height = 0;
Paint paint = new Paint();
int iHeight = 0;
int maxMemory = (int)(Runtime.getRuntime().maxMemory() / 1024);

// Use 1/8th of the available memory for this memory cache.
int cacheSize = maxMemory / 8;
LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize);
for (int i = 0; i < size; i++) {
RecyclerView.ViewHolder holder = adapter.createViewHolder(recyclerView, adapter.getItemViewType(i));
adapter.onBindViewHolder(holder, i);
holder.itemView.measure(
View.MeasureSpec.makeMeasureSpec(recyclerView.getWidth(), View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
holder.itemView.layout(0, 0, holder.itemView.getMeasuredWidth(),
holder.itemView.getMeasuredHeight());
holder.itemView.setBackgroundColor(Color.parseColor("#FFFFFF"));
holder.itemView.setDrawingCacheEnabled(true);
holder.itemView.buildDrawingCache();
Bitmap drawingCache = holder.itemView.getDrawingCache();
if (drawingCache != null) {
bitmapCache.put(i+"", drawingCache);
}
height += holder.itemView.getMeasuredHeight();
}

bigBitmap = Bitmap.createBitmap(recyclerView.getMeasuredWidth(), height, Bitmap.Config.ARGB_8888);
if (bigBitmap != null) {
Canvas bigCanvas = new Canvas(bigBitmap);
Drawable lBackground = recyclerView.getBackground();
if (lBackground instanceof ColorDrawable){
int lColor = ((ColorDrawable) lBackground).getColor();
bigCanvas.drawColor(lColor);
}

for (int i = 0; i < size; i++) {
Bitmap bitmap = bitmapCache.get(i+"");
bigCanvas.drawBitmap(bitmap, 0f, (float) iHeight, paint);
iHeight += bitmap.getHeight();
bitmap.recycle();
}
}
}
return bigBitmap;
}

/**
* 截取WebView,包含WebView的整个长度
* 在WebView渲染之前要加上以下代码,开启Html缓存,不然会截屏空白
* if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
* WebView.enableSlowWholeDocumentDraw()
* }
* WebView的截图很容易遇到内存溢出的问题,因为WebView可以加载很多内容,导致生成的图片特别长,创建Bitmap时容易OOM
*/
public Bitmap captureWebView(WebView webView) {
//&emsp;重新调用WebView的measure方法测量实际View的大小(将测量模式设置为UNSPECIFIED模式也就是需要多大就可以获得多大的空间)
webView.measure(View.MeasureSpec.makeMeasureSpec(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
//&emsp;调用layout方法设置布局(使用新测量的大小)
webView.layout(0, 0, webView.getMeasuredWidth(), webView.getMeasuredHeight());
//&emsp;开启WebView的缓存(当开启这个开关后下次调用getDrawingCache()方法的时候会把view绘制到一个bitmap上)
webView.setDrawingCacheEnabled(true);
//&emsp;强制绘制缓存(必须在setDrawingCacheEnabled(true)之后才能调用,否者需要手动调用destroyDrawingCache()清楚缓存)
webView.buildDrawingCache();

Bitmap bitmap = Bitmap.createBitmap(webView.getMeasuredWidth(), webView.getMeasuredHeight(), Bitmap.Config.ARGB_8888);

//&emsp;已picture为背景创建一个画布
Canvas canvas = new Canvas(bitmap); // 画布的宽高和 WebView 的网页保持一致
Paint paint = new Paint();
//&emsp;设置画笔的定点位置,也就是左上角
canvas.drawBitmap(bitmap, 0f, webView.getMeasuredHeight() * 1f, paint);
//&emsp;将WebView绘制在刚才创建的画板上
webView.draw(canvas);
webView.setDrawingCacheEnabled(false);
webView.destroyDrawingCache();
return bitmap;
}
}

图片选择器

Android图片处理:多合一,多张生成视频,裁剪,滤镜色调,饱和度,亮度,缩放调整

前言

Android图片美化处理的意义‌主要体现在以下几个方面:

  1. 增强用户体验‌:通过美化图片,可以显著提升用户的使用体验。美化后的图片在构图、色彩和细节上都能得到优化,使得整体画面通透、明亮,提升了视觉享受‌。
  2. 展现个性‌:美化处理的图片能够增加社交媒体垂直账号的独特个性,增强归属感,还能展示用户的审美品味和兴趣爱好。通过选择不同的壁纸背景,用户可以在社交场合中留下深刻印象‌1。
  3. 提升照片质量‌:通过美图功能,可以对照片进行裁剪、滤镜调节、亮度对比度调整等操作,从而提升照片的整体美观和质量。即使在光线条件不佳的情况下,也能轻松处理拍摄出的照片,使其焕发出炫目的艺术魅力‌2。
  4. 社会心理影响‌:在社交媒体上,经过精心修饰的照片往往能吸引更多的关注和点赞,成为个人形象和社交资本的一部分。这种对“精修”的追求反映了人们对于视觉享受和社会认同的需求‌。
  5. 技术进步的体现‌:随着智能手机摄像头的升级和修图软件的普及,人们开始有能力对自己的照片进行“精修”,从简单的磨皮美白到复杂的场景替换、风格转换,修图技术让每一张照片都有可能变成一幅艺术品‌3

本文重点从以下4个维度 对图片处理进行介绍:

  1. 多张图片拼合成一张
  2. 多张图片合成视频影片
  3. 图片的裁剪
  4. 图片大小缩放
  5. 图片的滤镜:色调,饱和度,亮度调整

多张图片拼合成一张

  1. Android 原生对图片合并处理主要是对突变bitmap进行处理:
    如下代码:是从左到右依次拼接

    当然可以调整成:
    从上到下依次拼接上下规则形网格,或者不规则形网格自定义拼接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private fun mergeImages(bitmaps: List<Bitmap>): Bitmap {
var totalWidth = 0
var maxHeight = 0
bitmaps.forEach { bitmap ->
totalWidth += bitmap.width //从左到右依次拼接
if (bitmap.height > maxHeight) maxHeight = bitmap.height
}
val result = Bitmap.createBitmap(totalWidth, maxHeight, Bitmap.Config.ARGB_8888)
val canvas = Canvas(result)
var currentX = 0
bitmaps.forEach { bitmap ->
canvas.drawBitmap(bitmap, currentX.toFloat(), 0f, null)
currentX += bitmap.width
}
return result
}
  1. 上面返回成bitmap 可以直接展示预览使用,也可以通过如下代码保存起来,注意文件读写权限
    1
    2
    3
    4
    5
    6
    private fun saveToLocal(bitmap: Bitmap) {
    val path = Environment.getExternalStorageDirectory().absolutePath + "/merged.jpg"
    FileOutputStream(path).use { fos ->
    bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos)
    }
    }

多张图片生成视频:

Android中通过图片生成视频步骤:

  1. 将图片先转成bitmap,然后再将bitmap转成YUV420(它是图像色彩存储的一种常见格式,如需要深入需要对视频编码原理进行自我相关知识补充)
  2. 通过原生MediaCodec对图片处理后的YUV420数据进行编码,编码结束通过MediaMuxer对编码后的数据进行封装成Mp4格式,也可以添加相关音频文件作为背景音乐。
  3. 关于MediaCodecMediaMuxer相关Api功能介绍,请参考我之前的文章:
    1. 如何拦截其他Android应用程序播放器的原始音频数据自定义保存下来?
    2. Android拦截其它播放声音:内录音,外录音,录屏,剪辑,混音,一键制作大片全搞定
  4. 需要注意的是:每张图片的宽高需要保持一致,如果大小不一,可以先自行进行相关放大缩小处理
  5. 具体代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109

class VideoEncoder(
private val outputPath: String, //视频输出路径
private val bitmaps: List<Bitmap>,//图片集
private val deltime: Int = 3000000, //每隔3秒切换一张图片
private val frameRate: Int = 30 //视频帧率1秒30帧
) {
private lateinit var mediaCodec: MediaCodec
private lateinit var mediaMuxer: MediaMuxer
private var trackIndex = -1
private var isMuxerStarted = false
private val timeoutUs = 10000L
private var mPresentationTime: Long = 0
private val KEY_I_FRAME_INTERVAL = 15
private val ptdivt = 1000000L

fun encode() {
val width = bitmaps.first().width
val height = bitmaps.first().height

// 1. 视频编码器相关配置
val mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, width, height).apply {
setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible)
// 视频码率 width heigth 帧 码率
setInteger(MediaFormat.KEY_BIT_RATE, width * height)
// 视频帧率 1秒 30帧
setInteger(MediaFormat.KEY_FRAME_RATE, frameRate)
// 视频每隔15帧一个I帧
setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, KEY_I_FRAME_INTERVAL)
}

// 2. 初始化组件
mediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC).apply {
configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
start()
}
mediaMuxer = MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
val count = deltime / (ptdivt / frameRate)
bitmaps.forEachIndexed { index, bitmap ->
val yuvData = bitmap.convertToYuv420()
for (i in 0 until count) {
mPresentationTime = computePresentationTime((count * index + i) * 1L)
// 输入缓冲区处理
mediaCodec.dequeueInputBuffer(timeoutUs).takeIf { it >= 0 }?.also { idx ->
mediaCodec.getInputBuffer(idx)?.put(yuvData)
mediaCodec.queueInputBuffer(idx, 0, yuvData.size, mPresentationTime, 0)
}
writeOutput()
}
}
// 5. 释放资源
mediaCodec.stop()
mediaCodec.release()
if (isMuxerStarted) mediaMuxer.stop()
mediaMuxer.release()
}

private fun writeOutput() {
while (true) {
val bufferInfo = MediaCodec.BufferInfo()
// 输出缓冲区处理
val bufferIndex = mediaCodec!!.dequeueOutputBuffer(bufferInfo, timeoutUs)
if (bufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
trackIndex = mediaMuxer.addTrack(mediaCodec!!.outputFormat)
mediaMuxer.start()
continue
}
if (bufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
break
}
if (trackIndex < 0) return
val buff = mediaCodec!!.getOutputBuffer(bufferIndex)
if (!((bufferInfo.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0 && bufferInfo!!.size != 0)) {
mediaMuxer.writeSampleData(trackIndex, buff!!, bufferInfo)
}
mediaCodec!!.releaseOutputBuffer(bufferIndex, false)
}
}

//计算出每张图片PresentationTime的视频编码的时间
private fun computePresentationTime(index: Long): Long {
return index * ptdivt / frameRate
}

// Bitmap转YUV420(关键实现)
private fun Bitmap.convertToYuv420(): ByteArray {
val yuv = ByteArray((width * height * 1.5).toInt())
val rgba = IntArray(width * height)
getPixels(rgba, 0, width, 0, 0, width, height)
var yIndex = 0
var uvIndex = width * height
for (i in 0 until height) {
for (j in 0 until width) {
val pixel = rgba[i * width + j]
val r = (pixel shr 16) and 0xFF
val g = (pixel shr 8) and 0xFF
val b = pixel and 0xFF
// Y分量
yuv[yIndex++] = (((66 * r + 129 * g + 25 * b + 128) shr 8) + 16).toByte()
// UV分量(每2x2像素采样一次)
if (i % 2 == 0 && j % 2 == 0) {
yuv[uvIndex++] = (((-38 * r - 74 * g + 112 * b + 128) shr 8) + 128).toByte()
yuv[uvIndex++] = (((112 * r - 94 * g - 18 * b + 128) shr 8) + 128).toByte()
}
}
}
return yuv
}
}
  1. 需要注意的是上面这个位置: 理论上是不需要对同一张图片的yuv数据进行重复编码, 如果图片比较少情况下,根据每张图片索引 index 计算出的时间 computePresentationTime 会很短,可能都不到1秒时间,这样视频时长很短,播放不了。
1
2
3
4
5
6
7
8
for (i in 0 until count) {
mPresentationTime = computePresentationTime((count * index + i) * 1L)
// 输入缓冲区处理
mediaCodec.dequeueInputBuffer(timeoutUs).takeIf { it >= 0 }?.also { idx ->
mediaCodec.getInputBuffer(idx)?.put(yuvData)
mediaCodec.queueInputBuffer(idx, 0, yuvData.size, mPresentationTime, 0)
}
writeOutput()
  1. 注意文件读写权限

图片裁剪

Android 原生对图片进行裁剪主要是对bitmap的处理,配置剪裁目标宽高:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 图片裁剪
* @param originalBitmap: 原始图片bitmap
* @param x: x为起始点坐标
* @param y: y为起始点坐标
* @param width: 裁剪区域的宽
* @param height: 裁剪区域的高
*
*/
fun imageCropper(originalBitmap: Bitmap, x: Int, y: Int, width: Int, height: Int) {
var croppedBitmap: Bitmap = Bitmap.createBitmap(originalBitmap, x, y, width, height)
}

图片放大缩小成指定尺寸后保存

Android 图片缩放主要是对Bitmap.scale进行操作,指定缩放后目标图片宽高,注意文件读写权限,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**图片放大缩小成指定尺寸后保存
* @param imagePath: 原始图片路径
* @param targetWidth: 缩放后的宽度
* @param targetHeight: 缩放后的高度
* @param outputPath: 图片输出路径
*/
fun resizeAndSaveImage(imagePath: String, targetWidth: Int, targetHeight: Int, outputPath: String) {
// 加载原始图片
val options = BitmapFactory.Options()
options.inPreferredConfig = Bitmap.Config.ARGB_8888
val originalBitmap = BitmapFactory.decodeFile(imagePath, options)
// 缩放图片
val resizedBitmap = originalBitmap.scale(targetWidth, targetHeight, false)
originalBitmap.recycle() // 回收原始位图资源
// 保存图片到指定路径
val outputFile = File(outputPath) // 可以根据需要更改文件名和格式
try {
FileOutputStream(outputFile).use { out ->
resizedBitmap.compress(Bitmap.CompressFormat.JPEG, 100, out) // 100表示最高质量,可以根据需要调整
}
} catch (e: IOException) {
e.printStackTrace()
} finally {
resizedBitmap.recycle() // 回收位图资源
}
}

图片滤镜色调,饱和度,亮度调整

Android 原始图片的滤镜也是对bitmap进行处理,结合ColorMatrixColorFilterColorMatrix使用

  1. ColorMatrixColorFilter : 是Android 开发中用于图形处理的一个重要工具,它通过颜色矩阵(ColorMatrix)来实现复杂的颜色变换效果。ColorMatrixColorFilter 属于Paint API的一部分,主要用于改变绘制的颜色,包括调整亮度、对比度、饱和度,或者应用特定的视觉效果如黑白、复古等‌
  2. ColorMatrix 是一种用于图像处理的技术,主要通过调整图像的RGB(红、绿、蓝)通道来改变图像的颜色效果。ColorMatrix是一个5x4的矩阵,可以通过修改矩阵中的元素来改变图像中RGBA各分量的值,从而实现各种颜色效果。
  3. 色调、饱和度、亮度 通过 **ColorMatrix的PostConcat相乘**,对原始图片进行变换处理使用代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
* 色调、饱和度、亮度 通过ColorMatrix的PostConcat相乘,对原始图片进行变换处理
* @param oriBmp
* @param hue 色调调节范围 -180度至180度
* @param saturation 取值范围是0到1 其中0表示灰度图像,1表示原图,取值越大,色彩饱和度越高‌
* @param lum
* @return
*/
fun handleImageEffect(oriBmp: Bitmap, hue: Float, saturation: Float, lum: Float): Bitmap {
val bmp = createBitmap(oriBmp.width, oriBmp.height)
val canvas = Canvas(bmp)
val paint: Paint = Paint()

//调节色调
val hueMatrix = ColorMatrix()
hueMatrix.setRotate(0, hue) //围绕red旋转 hue角度
hueMatrix.setRotate(1, hue) //围绕green旋转 hue角度
hueMatrix.setRotate(2, hue) //围绕blue旋转 hue角度

//调节饱和度
val saturationMatrix = ColorMatrix()
saturationMatrix.setSaturation(saturation)

//调节亮度
val lumMatrix = ColorMatrix()
//setScale方法接受四个参数:rScale、gScale、bScale和aScale,分别表示红色、绿色、蓝色和透明度的缩放比例
lumMatrix.setScale(lum, lum, lum, 1)

val imageMatrix = ColorMatrix()
imageMatrix.postConcat(hueMatrix)
imageMatrix.postConcat(saturationMatrix)
imageMatrix.postConcat(lumMatrix)

paint.setColorFilter(ColorMatrixColorFilter(imageMatrix))
canvas.drawBitmap(oriBmp, 0f, 0f, paint)
return bmp
}

4.‌ ‌ ColorMatrix.setRotate 方法:用于设置图像的色调旋转。该方法接受两个参数:轴(axis)和旋转角度(degree)。
其中参数:

  • axis‌:指定旋转的轴,可以是0、1或2,分别代表红色(R)、绿色(G)、蓝色(B)。
  • degree‌:旋转的角度,取值范围为-180度至180度。

ColorMatrix的setSaturation 方法用于设置图像的饱和度。ColorMatrix是一个4x5的矩阵,通过矩阵乘法和加法实现颜色的转换。setSaturation方法:通过调整颜色矩阵中的元素来改变图像的饱和度。具体来说,setSaturation方法会将颜色矩阵重置为一个初始状态,然后根据传入的饱和度参数调整R、G、B三个颜色的权重,从而实现饱和度的变化。

  • 饱和度参数sat的取值范围是: 0到1,其中0表示灰度图像,1表示原图,取值越大,色彩饱和度越高‌

ColorMatrix.setScale 方法用于设置颜色矩阵的比例缩放值‌。
该方法接受四个参数:

  • rScale、gScale、bScale和aScale,分别表示红色、绿色、蓝色和透明度的缩放比例
  1. 也可以直接通过ColorMatrix.set(矩阵数组):比如:将矩阵中的某些值设置为0可以实现黑白效果
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    黑白效果
    val mBWMatrix = floatArrayOf(
    1f, 0f, 0f, 0f, 0f,
    0f, 1f, 0f, 0f, 0f,
    0f, 0f, 1f, 0f, 0f,
    0f, 0f, 0f, 1f, 0f
    )


    //设置矩阵数组
    fun setImageMatrix(mBitmap:Bitmap,mColorMatrix: FloatArray) {
    val bmp = createBitmap(mBitmap.getWidth(), mBitmap.getHeight())
    val colorMatrix = ColorMatrix()
    colorMatrix.set(mColorMatrix)
    val canvas = Canvas(bmp)
    val paint = Paint()
    paint.setColorFilter(ColorMatrixColorFilter(colorMatrix))
    canvas.drawBitmap(mBitmap, 0f, 0f, paint)
    ivImage.setImageBitmap(bmp)
    }

总结

本文重点介绍了:

  1. 如何实现多张图片拼合成一张
  2. 如何实现多张图片合成视频影片
  3. 如何实现图片的裁剪
  4. 如何实现图片大小缩放
  5. 如何实现图片的滤镜:色调,饱和度,亮度调整