Android建筑图像过滤器,如Instagram
现在图像过滤器在很多Android应用程序中很常见。Instagram以其流行的过滤器功能而闻名,可能是第一款将图像过滤器引入Android世界的应用程序。还有很多其他图像编辑应用程序提供图像过滤器和图像编辑功能。
在本文中,我们将学习如何构建像Instagram这样的图像过滤器应用程序。我们不会介绍精确的过滤器开发,但我们使用现有的图像过滤器库。
下载代码下载.APK GITHUB
1.如何构建图像滤镜
通常,图像处理操作将以本机C / C ++语言完成。在android中,您可以使用C或C ++编写库,并使用JNI(Java Native Interface)通过Java代码访问函数。您还可以考虑使用像openCV这样的流行图像处理库来创建自己的过滤器库。
在编写本机模块时,配置JNI是一个单独的主题,我想在另一篇文章中介绍它。现在我们将考虑使用本文中的现有图像过滤器库。
2.使用图书馆(Zomato,Androidhive)
在这篇文章中,我认为使用图像滤波器库AndroidPhotoFilters通过开发Zomato。该库提供基本的图像操作,如控制亮度,饱和度,对比度和少量图像滤镜。将所有这些功能组合在一起,您可以创建您赢得的过滤器。我对库进行了一些即兴创作并将其托管在公共maven存储库中,以便您可以非常轻松地集成到您的项目中。
还记得库是非常基本的,你不能用Instagram实现像Instagram这样的伟大过滤器。要像Instagram一样构建过滤器,许多基础工作必须在本机级别完成。但我们会尝试实现类似于Instagram的过滤器。
要使用该库,请在项目的依赖项中添加info.androidhive:imagefilters:1.0.7。
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
// ...
implementation 'info.androidhive:imagefilters:1.0.7'
}
有关该库的更详细的文档可以在Github页面上找到。
3.图像过滤包
我写了几个图像过滤器并将它们包含在库中。您可以使用下面的代码段获取Filter Pack。您可以循环它们并渲染每个过滤器的缩略图版本。您也可以直接访问单个过滤器。
// get the filter pack
List filters = FilterPack.getFilterPack(getActivity());
for(Filter filter : filters) {
ThumbnailItem item = newThumbnailItem();
item.image = thumbImage;
item.filter = filter;
item.filterName = filter.getName();
ThumbnailsManager.addThumb(tI);
}
// Accessing single filter...
Bitmap bitmap = your_bitmap_;
Filter clarendon = FilterPack.getClarendon();
// apply filter
imagePreview.setImageBitmap(filter.processFilter(bitmap));
下面是每个过滤器的预览及其名称。
过滤包
施特鲁克克拉伦登奥德曼
火星上升四月
亚马逊星光耳语
青柠阿里汉Bluemess
阿黛尔克鲁兹都会
奥黛丽
4.构建Instagram界面
这里的想法是像Instagram一样构建界面,底部有两个标签。一个选项卡用于应用不同的滤镜,另一个用于控制图像调整,如亮度,对比度和饱和度。
要实现此布局,我们需要将ViewPager与TabLayout结合使用。要在可滚动列表中呈现缩略图图像,需要使用RecyclerView。我们还需要两个Fragment类,一个用于渲染水平缩略图以预览滤镜效果。另一个片段是显示图像控件。
如果您观察下面的图像,MainActivity.java用于实现整个布局。ImageFiltersFragment.java用于渲染水平缩略图图像。EditImageFrament.java用于渲染图像调整控件。每当应用过滤器或更改图像控件时,这两个片段都将提供回调方法。在主要活动中,将在回调时采取适当的行动。
现在让我们从Android Studio中的一个新项目开始吧。
1。从File⇒NewProject在Android Studio中创建一个新项目,然后从模板中选择Basic Activity。
2。开放的build.gradle位于下的应用程序文件夹,并添加androidhive的ImageFilters依赖。我还添加了其他必要的依赖项,如ButterKnife,Dexter和RecyclerView。
的build.gradle
dependencies {
// ...
// image filters
implementation 'info.androidhive:imagefilters:1.0.7'
// butter knife
compile 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
// dexter M permissions
compile 'com.karumi:dexter:4.1.0'
compile 'com.android.support:recyclerview-v7:26.1.0'
}
3。将以下资源添加到相应的strings.xml,colors.xml,dimens.xml和styles.xml文件中。
strings.xml中
Image Filters
Filters
Settings
FILTERS
EDIT
BRIGHTNESS
CONTRAST
SATURATION
FILTERS
EDIT
sans-serif-medium
Normal
SAVE
OPEN
colors.xml
#3F51B5
#303F9F
#009688
#FF3990
#8A8889
#221F20
dimens.xml
16dp
80dp
100dp
8dp
10dp
10dp
16dp
100dp
styles.xml
@color/colorPrimary
@color/colorPrimaryDark
@color/colorAccent
false
true
false
true
true
@color/color_option_menu
4。打开AndroidManifest.xml并添加READ_EXTERNAL_STORAGE,WRITE_EXTERNAL_STORAGE权限。将AppTheme.NoActionBar.Fullscreen主题添加到主活动以使活动全屏显示。
AndroidManifest.xml中
http://schemas.android.com/apk/res/android"
package="info.androidhive.imagefilters">
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
android:name=".MainActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar.Fullscreen">
5。打开位于res⇒ 菜单文件夹下的menu_main.xml,然后修改菜单项,如下所示。此菜单提供工具栏中的OPEN和SAVE选项以打开和保存图像。
menu_main.xml
http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="info.androidhive.imagefilters.MainActivity">
android:id="@+id/action_open"
android:orderInCategory="100"
android:title="@string/action_open"
app:showAsAction="always"/>
android:id="@+id/action_save"
android:orderInCategory="101"
android:title="@string/action_save"
app:showAsAction="always"/>
6。创建一个名为utils的新包。在这里,我们将保留此应用程序所需的辅助类。
7。在utils包下,创建一个名为BitmapUtils.java的类。该类将具有从库加载图像,压缩图像,将图像保存到图库的方法。
BitmapUtils.java
packageinfo.androidhive.imagefilters.utils;
importandroid.content.ContentResolver;
importandroid.content.ContentUris;
importandroid.content.ContentValues;
importandroid.content.Context;
importandroid.content.res.AssetManager;
importandroid.content.res.Resources;
importandroid.database.Cursor;
importandroid.graphics.Bitmap;
importandroid.graphics.BitmapFactory;
importandroid.graphics.Matrix;
importandroid.net.Uri;
importandroid.provider.MediaStore;
importandroid.util.Log;
importjava.io.FileNotFoundException;
importjava.io.IOException;
importjava.io.InputStream;
importjava.io.OutputStream;
/**
* Created by ravi on 06/11/17.
*/
publicclassBitmapUtils {
privatestaticfinalString TAG = BitmapUtils.class.getSimpleName();
/**
* Getting bitmap from Assets folder
*
* @return
*/
publicstaticBitmap getBitmapFromAssets(Context context, String fileName, intwidth, intheight) {
AssetManager assetManager = context.getAssets();
InputStream istr;
Bitmap bitmap = null;
try{
finalBitmapFactory.Options options = newBitmapFactory.Options();
options.inJustDecodeBounds = true;
istr = assetManager.open(fileName);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, width, height);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
returnBitmapFactory.decodeStream(istr, null, options);
} catch(IOException e) {
Log.e(TAG, "Exception: "+ e.getMessage());
}
returnnull;
}
/**
* Getting bitmap from Gallery
*
* @return
*/
publicstaticBitmap getBitmapFromGallery(Context context, Uri path, intwidth, intheight) {
String[] filePathColumn = {MediaStore.Images.Media.DATA};
Cursor cursor = context.getContentResolver().query(path, filePathColumn, null, null, null);
cursor.moveToFirst();
intcolumnIndex = cursor.getColumnIndex(filePathColumn[0]);
String picturePath = cursor.getString(columnIndex);
cursor.close();
finalBitmapFactory.Options options = newBitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(picturePath, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, width, height);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
returnBitmapFactory.decodeFile(picturePath, options);
}
privatestaticintcalculateInSampleSize(
BitmapFactory.Options options, intreqWidth, intreqHeight) {
// Raw height and width of image
finalintheight = options.outHeight;
finalintwidth = options.outWidth;
intinSampleSize = 1;
if(height > reqHeight || width > reqWidth) {
finalinthalfHeight = height / 2;
finalinthalfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
returninSampleSize;
}
publicstaticBitmap decodeSampledBitmapFromResource(Resources res, intresId,
intreqWidth, intreqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
finalBitmapFactory.Options options = newBitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
returnBitmapFactory.decodeResource(res, resId, options);
}
/**
* Storing image to device gallery
* @param cr
* @param source
* @param title
* @param description
* @return
*/
publicstaticfinalString insertImage(ContentResolver cr,
Bitmap source,
String title,
String description) {
ContentValues values = newContentValues();
values.put(MediaStore.Images.Media.TITLE, title);
values.put(MediaStore.Images.Media.DISPLAY_NAME, title);
values.put(MediaStore.Images.Media.DESCRIPTION, description);
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
// Add the date meta data to ensure the image is added at the front of the gallery
values.put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis());
values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
Uri url = null;
String stringUrl = null; /* value to be returned */
try{
url = cr.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
if(source != null) {
OutputStream imageOut = cr.openOutputStream(url);
try{
source.compress(Bitmap.CompressFormat.JPEG, 50, imageOut);
} finally{
imageOut.close();
}
longid = ContentUris.parseId(url);
// Wait until MINI_KIND thumbnail is generated.
Bitmap miniThumb = MediaStore.Images.Thumbnails.getThumbnail(cr, id, MediaStore.Images.Thumbnails.MINI_KIND, null);
// This is for backward compatibility.
storeThumbnail(cr, miniThumb, id, 50F, 50F, MediaStore.Images.Thumbnails.MICRO_KIND);
} else{
cr.delete(url, null, null);
url = null;
}
} catch(Exception e) {
if(url != null) {
cr.delete(url, null, null);
url = null;
}
}
if(url != null) {
stringUrl = url.toString();
}
returnstringUrl;
}
/**
* A copy of the Android internals StoreThumbnail method, it used with the insertImage to
* populate the android.provider.MediaStore.Images.Media#insertImage with all the correct
* meta data. The StoreThumbnail method is private so it must be duplicated here.
*
* @see android.provider.MediaStore.Images.Media (StoreThumbnail private method)
*/
privatestaticfinalBitmap storeThumbnail(
ContentResolver cr,
Bitmap source,
longid,
floatwidth,
floatheight,
intkind) {
// create the matrix to scale it
Matrix matrix = newMatrix();
floatscaleX = width / source.getWidth();
floatscaleY = height / source.getHeight();
matrix.setScale(scaleX, scaleY);
Bitmap thumb = Bitmap.createBitmap(source, 0, 0,
source.getWidth(),
source.getHeight(), matrix,
true
);
ContentValues values = newContentValues(4);
values.put(MediaStore.Images.Thumbnails.KIND, kind);
values.put(MediaStore.Images.Thumbnails.IMAGE_ID, (int) id);
values.put(MediaStore.Images.Thumbnails.HEIGHT, thumb.getHeight());
values.put(MediaStore.Images.Thumbnails.WIDTH, thumb.getWidth());
Uri url = cr.insert(MediaStore.Images.Thumbnails.EXTERNAL_CONTENT_URI, values);
try{
OutputStream thumbOut = cr.openOutputStream(url);
thumb.compress(Bitmap.CompressFormat.JPEG, 100, thumbOut);
thumbOut.close();
returnthumb;
} catch(FileNotFoundException ex) {
returnnull;
} catch(IOException ex) {
returnnull;
}
}
}
8。在utils包下,创建一个名为NonSwipeableViewPager.java的类。这是自定义ViewPager元素,用于禁用页面之间的滑动功能。
NonSwipeableViewPager.java
packageinfo.androidhive.imagefilters.utils;
importandroid.content.Context;
importandroid.support.v4.view.ViewPager;
importandroid.util.AttributeSet;
importandroid.view.MotionEvent;
importandroid.view.animation.DecelerateInterpolator;
importandroid.widget.Scroller;
importjava.lang.reflect.Field;
/**
* Created by ravi on 24/10/17.
* Custom viewpager disabling the swipe
*https://stackoverflow.com/questions/9650265/how-do-disable-paging-by-swiping-with-finger-in-viewpager-but-still-be-able-to-s
*/
publicclassNonSwipeableViewPager extendsViewPager {
publicNonSwipeableViewPager(Context context) {
super(context);
setMyScroller();
}
publicNonSwipeableViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
setMyScroller();
}
@Override
publicbooleanonInterceptTouchEvent(MotionEvent event) {
// Never allow swiping to switch between pages
returnfalse;
}
@Override
publicbooleanonTouchEvent(MotionEvent event) {
// Never allow swiping to switch between pages
returnfalse;
}
//down one is added for smooth scrolling
privatevoidsetMyScroller() {
try{
Class viewpager = ViewPager.class;
Field scroller = viewpager.getDeclaredField("mScroller");
scroller.setAccessible(true);
scroller.set(this, newMyScroller(getContext()));
} catch(Exception e) {
e.printStackTrace();
}
}
publicclassMyScroller extendsScroller {
publicMyScroller(Context context) {
super(context, newDecelerateInterpolator());
}
@Override
publicvoidstartScroll(intstartX, intstartY, intdx, intdy, intduration) {
super.startScroll(startX, startY, dx, dy, 350/*1 secs*/);
}
}
}
9。在utils下创建另一个名为SpacesItemDecoration.java的类。这个类是在RecyclerView缩略图图像周围添加填充。padding-right将添加到所有缩略图图像中,但不会添加到列表中的最后一项。
SpacesItemDecoration.java
packageinfo.androidhive.imagefilters.utils;
importandroid.graphics.Rect;
importandroid.support.v7.widget.RecyclerView;
importandroid.view.View;
/**
* Created by ravi on 23/10/17.
*/
publicclassSpacesItemDecoration extendsRecyclerView.ItemDecoration {
privateintspace;
publicSpacesItemDecoration(intspace) {
this.space = space;
}
@Override
publicvoidgetItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
if(parent.getChildAdapterPosition(view) == state.getItemCount() - 1) {
outRect.left = space;
outRect.right = 0;
}else{
outRect.right = space;
outRect.left = 0;
}
}
}
5.水平缩略图RecyclerView适配器
现在我们准备好了所有必需的课程。在跳转到实际UI之前,让我们创建RecyclerView适配器类。
10。在res⇒布局下创建一个名为thumbnail_list_item.xml的新布局文件。此布局包含TextView和ImageView以显示过滤器名称和缩略图图像。
thumbnail_list_item.xml
http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical">
android:id="@+id/filter_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="5dp"
android:layout_marginTop="5dp"
android:fontFamily="@string/roboto_medium"/>
android:id="@+id/thumbnail"
android:layout_width="@dimen/thumbnail_size"
android:layout_height="@dimen/thumbnail_size"
android:src="@mipmap/ic_launcher"/>
11。创建一个名为ThumbnailsAdapter.java的类此类充当RecyclerView缩略图适配器,以在水平列表中显示已过滤的图像。
ThumbnailsAdapter.java
packageinfo.androidhive.imagefilters;
importandroid.content.Context;
importandroid.support.v4.content.ContextCompat;
importandroid.support.v7.widget.RecyclerView;
importandroid.view.LayoutInflater;
importandroid.view.View;
importandroid.view.ViewGroup;
importandroid.widget.ImageView;
importandroid.widget.TextView;
importcom.zomato.photofilters.imageprocessors.Filter;
importcom.zomato.photofilters.utils.ThumbnailItem;
importjava.util.List;
importbutterknife.BindView;
importbutterknife.ButterKnife;
/**
* Created by ravi on 23/10/17.
*/
publicclassThumbnailsAdapter extendsRecyclerView.Adapter {
privateList thumbnailItemList;
privateThumbnailsAdapterListener listener;
privateContext mContext;
privateintselectedIndex = 0;
publicclassMyViewHolder extendsRecyclerView.ViewHolder {
@BindView(R.id.thumbnail)
ImageView thumbnail;
@BindView(R.id.filter_name)
TextView filterName;
publicMyViewHolder(View view) {
super(view);
ButterKnife.bind(this, view);
}
}
publicThumbnailsAdapter(Context context, List thumbnailItemList, ThumbnailsAdapterListener listener) {
mContext = context;
this.thumbnailItemList = thumbnailItemList;
this.listener = listener;
}
@Override
publicMyViewHolder onCreateViewHolder(ViewGroup parent, intviewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.thumbnail_list_item, parent, false);
returnnewMyViewHolder(itemView);
}
@Override
publicvoidonBindViewHolder(MyViewHolder holder, finalintposition) {
finalThumbnailItem thumbnailItem = thumbnailItemList.get(position);
holder.thumbnail.setImageBitmap(thumbnailItem.image);
holder.thumbnail.setOnClickListener(newView.OnClickListener() {
@Override
publicvoidonClick(View view) {
listener.onFilterSelected(thumbnailItem.filter);
selectedIndex = position;
notifyDataSetChanged();
}
});
holder.filterName.setText(thumbnailItem.filterName);
if(selectedIndex == position) {
holder.filterName.setTextColor(ContextCompat.getColor(mContext, R.color.filter_label_selected));
} else{
holder.filterName.setTextColor(ContextCompat.getColor(mContext, R.color.filter_label_normal));
}
}
@Override
publicintgetItemCount() {
returnthumbnailItemList.size();
}
publicinterfaceThumbnailsAdapterListener {
voidonFilterSelected(Filter filter);
}
}
6.添加图像过滤器列表片段
现在我们将创建一个Fragment类,以在水平列表中呈现过滤后的图像缩略图。为此,我们需要一个RecyclerView并提供缩略图列表到适配器类。
12。通过转到文件⇒新⇒片段⇒片段(空白)创建一个新片段,并将其命名为FiltersListFragment.java。这将创建一个包含必要布局文件的空白片段。
13。打开片段的布局文件,即fragment_filters_list.xml,并添加RecyclerView元素。
fragment_filters_list.xml
http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="info.androidhive.imagefilters.FiltersListFragment">
android:id="@+id/recycler_view"
android:layout_gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:padding="4dp"
android:scrollbars="none"/>