14。打开FiltersListFragment.java并进行如下所示的修改。这里
> FilterPack.getFilterPack()提供库中可用过滤器的列表。
>在prepareThumbnail()方法中,过滤器被填充,并且每个缩略图项目都被添加到ThumbnailsManager以处理它们。处理后的缩略图将添加回thumbnailItemList,后者是RecyclerView的数据资源。
>缩略图数据集准备就绪后,调用mAdapter.notifyDataSetChanged()来渲染列表。所有这些都是在后台线程中完成的,因为图像处理需要一段时间,我们不应该阻止主线程。
>每当选择新过滤器时,FiltersListFragmentListener接口都会为主活动提供回调方法。
> MainActivity中将对所选图像过滤器的实际处理进行处理。
FiltersListFragment.java
packageinfo.androidhive.imagefilters;
importandroid.graphics.Bitmap;
importandroid.os.Bundle;
importandroid.support.v4.app.Fragment;
importandroid.support.v7.widget.DefaultItemAnimator;
importandroid.support.v7.widget.LinearLayoutManager;
importandroid.support.v7.widget.RecyclerView;
importandroid.util.Log;
importandroid.util.TypedValue;
importandroid.view.LayoutInflater;
importandroid.view.View;
importandroid.view.ViewGroup;
importcom.zomato.photofilters.FilterPack;
importcom.zomato.photofilters.imageprocessors.Filter;
importcom.zomato.photofilters.utils.ThumbnailItem;
importcom.zomato.photofilters.utils.ThumbnailsManager;
importjava.util.ArrayList;
importjava.util.HashMap;
importjava.util.List;
importjava.util.Map;
importbutterknife.BindView;
importbutterknife.ButterKnife;
importinfo.androidhive.imagefilters.utils.BitmapUtils;
importinfo.androidhive.imagefilters.utils.SpacesItemDecoration;
publicclassFiltersListFragment extendsFragment implementsThumbnailsAdapter.ThumbnailsAdapterListener {
@BindView(R.id.recycler_view)
RecyclerView recyclerView;
ThumbnailsAdapter mAdapter;
List thumbnailItemList;
FiltersListFragmentListener listener;
publicvoidsetListener(FiltersListFragmentListener listener) {
this.listener = listener;
}
publicFiltersListFragment() {
// Required empty public constructor
}
@Override
publicvoidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
publicView onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_filters_list, container, false);
ButterKnife.bind(this, view);
thumbnailItemList = newArrayList<>();
mAdapter = newThumbnailsAdapter(getActivity(), thumbnailItemList, this);
RecyclerView.LayoutManager mLayoutManager = newLinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false);
recyclerView.setLayoutManager(mLayoutManager);
recyclerView.setItemAnimator(newDefaultItemAnimator());
intspace = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8,
getResources().getDisplayMetrics());
recyclerView.addItemDecoration(newSpacesItemDecoration(space));
recyclerView.setAdapter(mAdapter);
prepareThumbnail(null);
returnview;
}
/**
* Renders thumbnails in horizontal list
* loads default image from Assets if passed param is null
*
* @param bitmap
*/
publicvoidprepareThumbnail(finalBitmap bitmap) {
Runnable r = newRunnable() {
publicvoidrun() {
Bitmap thumbImage;
if(bitmap == null) {
thumbImage = BitmapUtils.getBitmapFromAssets(getActivity(), MainActivity.IMAGE_NAME, 100, 100);
} else{
thumbImage = Bitmap.createScaledBitmap(bitmap, 100, 100, false);
}
if(thumbImage == null)
return;
ThumbnailsManager.clearThumbs();
thumbnailItemList.clear();
// add normal bitmap first
ThumbnailItem thumbnailItem = newThumbnailItem();
thumbnailItem.image = thumbImage;
thumbnailItem.filterName = getString(R.string.filter_normal);
ThumbnailsManager.addThumb(thumbnailItem);
List filters = FilterPack.getFilterPack(getActivity());
for(Filter filter : filters) {
ThumbnailItem tI = newThumbnailItem();
tI.image = thumbImage;
tI.filter = filter;
tI.filterName = filter.getName();
ThumbnailsManager.addThumb(tI);
}
thumbnailItemList.addAll(ThumbnailsManager.processThumbs(getActivity()));
getActivity().runOnUiThread(newRunnable() {
@Override
publicvoidrun() {
mAdapter.notifyDataSetChanged();
}
});
}
};
newThread(r).start();
}
@Override
publicvoidonFilterSelected(Filter filter) {
if(listener != null)
listener.onFilterSelected(filter);
}
publicinterfaceFiltersListFragmentListener {
voidonFilterSelected(Filter filter);
}
}
7.添加图像编辑控件片段
现在我们将添加图像控件片段来控制亮度,对比度和饱和度。
15。按照与上一步相同的步骤再创建一个名为EditImageFragment.java的片段。打开此片段fragment_edit_image.xml的布局文件,并执行如下修改。
在这个布局中,我们将添加三个SeekBar小部件来控制图像亮度,对比度和饱和度。
fragment_edit_image.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"
android:gravity="center_vertical"
android:orientation="vertical"
android:paddingLeft="@dimen/margin_horizontal"
android:paddingRight="@dimen/margin_horizontal"
tools:context="info.androidhive.imagefilters.EditImageFragment">
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingBottom="@dimen/padding_10"
android:paddingTop="@dimen/padding_10">
android:layout_width="@dimen/lbl_edit_image_control"
android:layout_height="wrap_content"
android:text="@string/lbl_brightness"/>
android:id="@+id/seekbar_brightness"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"/>
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingBottom="@dimen/padding_10"
android:paddingTop="@dimen/padding_10">
android:layout_width="@dimen/lbl_edit_image_control"
android:layout_height="wrap_content"
android:text="@string/lbl_contrast"/>
android:id="@+id/seekbar_contrast"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"/>
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingBottom="@dimen/padding_10"
android:paddingTop="@dimen/padding_10">
android:layout_width="@dimen/lbl_edit_image_control"
android:layout_height="wrap_content"
android:text="@string/lbl_saturation"/>
android:id="@+id/seekbar_saturation"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"/>
16。打开EditImageFragment.java并进行如下修改。
>在onCreateView方法中,Seekbar小部件使用初始值和最大值进行初始化。对于亮度,值可以在-100 / +100之间。在对比度和饱和度采取浮动值。
>每当Seekbar值发生更改时,EditImageFragmentListener接口都会提供回调方法。
>在回调时,MainActivity再次处理亮度,对比度和饱和度。
EditImageFragment.java
packageinfo.androidhive.imagefilters;
importandroid.os.Bundle;
importandroid.support.v4.app.Fragment;
importandroid.util.Log;
importandroid.view.LayoutInflater;
importandroid.view.View;
importandroid.view.ViewGroup;
importandroid.widget.SeekBar;
importbutterknife.BindView;
importbutterknife.ButterKnife;
publicclassEditImageFragment extendsFragment implementsSeekBar.OnSeekBarChangeListener {
privateEditImageFragmentListener listener;
@BindView(R.id.seekbar_brightness)
SeekBar seekBarBrightness;
@BindView(R.id.seekbar_contrast)
SeekBar seekBarContrast;
@BindView(R.id.seekbar_saturation)
SeekBar seekBarSaturation;
publicvoidsetListener(EditImageFragmentListener listener) {
this.listener = listener;
}
publicEditImageFragment() {
// Required empty public constructor
}
@Override
publicvoidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
publicView onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_edit_image, container, false);
ButterKnife.bind(this, view);
// keeping brightness value b/w -100 / +100
seekBarBrightness.setMax(200);
seekBarBrightness.setProgress(100);
// keeping contrast value b/w 1.0 - 3.0
seekBarContrast.setMax(20);
seekBarContrast.setProgress(0);
// keeping saturation value b/w 0.0 - 3.0
seekBarSaturation.setMax(30);
seekBarSaturation.setProgress(10);
seekBarBrightness.setOnSeekBarChangeListener(this);
seekBarContrast.setOnSeekBarChangeListener(this);
seekBarSaturation.setOnSeekBarChangeListener(this);
returnview;
}
@Override
publicvoidonProgressChanged(SeekBar seekBar, intprogress, booleanb) {
if(listener != null) {
if(seekBar.getId() == R.id.seekbar_brightness) {
// brightness values are b/w -100 to +100
listener.onBrightnessChanged(progress - 100);
}
if(seekBar.getId() == R.id.seekbar_contrast) {
// converting int value to float
// contrast values are b/w 1.0f - 3.0f
// progress = progress > 10 ? progress : 10;
progress += 10;
floatfloatVal = .10f * progress;
listener.onContrastChanged(floatVal);
}
if(seekBar.getId() == R.id.seekbar_saturation) {
// converting int value to float
// saturation values are b/w 0.0f - 3.0f
floatfloatVal = .10f * progress;
listener.onSaturationChanged(floatVal);
}
}
}
@Override
publicvoidonStartTrackingTouch(SeekBar seekBar) {
if(listener != null)
listener.onEditStarted();
}
@Override
publicvoidonStopTrackingTouch(SeekBar seekBar) {
if(listener != null)
listener.onEditCompleted();
}
publicvoidresetControls() {
seekBarBrightness.setProgress(100);
seekBarContrast.setProgress(0);
seekBarSaturation.setProgress(10);
}
publicinterfaceEditImageFragmentListener {
voidonBrightnessChanged(intbrightness);
voidonSaturationChanged(floatsaturation);
voidonContrastChanged(floatcontrast);
voidonEditStarted();
voidonEditCompleted();
}
}
8.实现主界面(组合片段)
现在我们准备好了片段,让我们看看如何将它们组合起来以实现我们的最终输出。
17。打开主活动activity_main.xml和content_main.xml以及NonSwipeableViewPager和TabLayout的布局文件。
activity_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"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="info.androidhive.imagefilters.MainActivity">
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@android:color/white"
app:popupTheme="@style/AppTheme.PopupOverlay"/>
content_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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:orientation="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="info.androidhive.imagefilters.MainActivity"
tools:showIn="@layout/activity_main">
android:id="@+id/image_preview"
android:layout_width="match_parent"
android:layout_height="360dp"
android:scaleType="centerCrop"/>
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="120dp"
android:layout_above="@+id/tabs"
android:layout_below="@+id/image_preview"
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
app:tabGravity="fill"
app:tabMode="fixed"/>
18。打开MainActivity.java并执行如下所示的更改。
>调用System.loadLibrary(“NativeImageProcessor”)来初始化本机库。
> FiltersListFragment和EditImageFragments加到ViewPager在setupViewPager()方法。
> onFilterSelected()当在被选择的过滤器将被称为FiltersListFragment。处理选定的过滤器,并显示最终图像imagePreview。
> onBrightnessChanged(),onSaturationChanged()和onContrastChanged()方法将在EditImageFragments中的Seekbar值更改时调用。
>从工具栏中选择“保存”选项后,最终图像将保存到图库。
MainActivity.java
packageinfo.androidhive.imagefilters;
importandroid.Manifest;
importandroid.content.Intent;
importandroid.graphics.Bitmap;
importandroid.net.Uri;
importandroid.os.Bundle;
importandroid.support.design.widget.CoordinatorLayout;
importandroid.support.design.widget.Snackbar;
importandroid.support.design.widget.TabLayout;
importandroid.support.v4.app.Fragment;
importandroid.support.v4.app.FragmentManager;
importandroid.support.v4.app.FragmentPagerAdapter;
importandroid.support.v4.view.ViewPager;
importandroid.support.v7.app.AppCompatActivity;
importandroid.support.v7.widget.Toolbar;
importandroid.text.TextUtils;
importandroid.util.Log;
importandroid.view.Menu;
importandroid.view.MenuItem;
importandroid.view.View;
importandroid.widget.ImageView;
importandroid.widget.Toast;
importcom.karumi.dexter.Dexter;
importcom.karumi.dexter.MultiplePermissionsReport;
importcom.karumi.dexter.PermissionToken;
importcom.karumi.dexter.listener.PermissionRequest;
importcom.karumi.dexter.listener.multi.MultiplePermissionsListener;
importcom.zomato.photofilters.imageprocessors.Filter;
importcom.zomato.photofilters.imageprocessors.subfilters.BrightnessSubFilter;
importcom.zomato.photofilters.imageprocessors.subfilters.ContrastSubFilter;
importcom.zomato.photofilters.imageprocessors.subfilters.SaturationSubfilter;
importjava.util.ArrayList;
importjava.util.List;
importbutterknife.BindView;
importbutterknife.ButterKnife;
importinfo.androidhive.imagefilters.utils.BitmapUtils;
publicclassMainActivity extendsAppCompatActivity implementsFiltersListFragment.FiltersListFragmentListener, EditImageFragment.EditImageFragmentListener {
privatestaticfinalString TAG = MainActivity.class.getSimpleName();
publicstaticfinalString IMAGE_NAME = "dog.jpg";
publicstaticfinalintSELECT_GALLERY_IMAGE = 101;
@BindView(R.id.image_preview)
ImageView imagePreview;
@BindView(R.id.tabs)
TabLayout tabLayout;
@BindView(R.id.viewpager)
ViewPager viewPager;
@BindView(R.id.coordinator_layout)
CoordinatorLayout coordinatorLayout;
Bitmap originalImage;
// to backup image with filter applied
Bitmap filteredImage;
// the final image after applying
// brightness, saturation, contrast
Bitmap finalImage;
FiltersListFragment filtersListFragment;
EditImageFragment editImageFragment;
// modified image values
intbrightnessFinal = 0;
floatsaturationFinal = 1.0f;
floatcontrastFinal = 1.0f;
// load native image filters library
static{
System.loadLibrary("NativeImageProcessor");
}
@Override
protectedvoidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setTitle(getString(R.string.activity_title_main));
loadImage();
setupViewPager(viewPager);
tabLayout.setupWithViewPager(viewPager);
}
privatevoidsetupViewPager(ViewPager viewPager) {
ViewPagerAdapter adapter = newViewPagerAdapter(getSupportFragmentManager());
// adding filter list fragment
filtersListFragment = newFiltersListFragment();
filtersListFragment.setListener(this);
// adding edit image fragment
editImageFragment = newEditImageFragment();
editImageFragment.setListener(this);
adapter.addFragment(filtersListFragment, getString(R.string.tab_filters));
adapter.addFragment(editImageFragment, getString(R.string.tab_edit));
viewPager.setAdapter(adapter);
}
@Override
publicvoidonFilterSelected(Filter filter) {
// reset image controls
resetControls();
// applying the selected filter
filteredImage = originalImage.copy(Bitmap.Config.ARGB_8888, true);
// preview filtered image
imagePreview.setImageBitmap(filter.processFilter(filteredImage));
finalImage = filteredImage.copy(Bitmap.Config.ARGB_8888, true);
}
@Override
publicvoidonBrightnessChanged(finalintbrightness) {
brightnessFinal = brightness;
Filter myFilter = newFilter();
myFilter.addSubFilter(newBrightnessSubFilter(brightness));
imagePreview.setImageBitmap(myFilter.processFilter(finalImage.copy(Bitmap.Config.ARGB_8888, true)));
}
@Override
publicvoidonSaturationChanged(finalfloatsaturation) {
saturationFinal = saturation;
Filter myFilter = newFilter();
myFilter.addSubFilter(newSaturationSubfilter(saturation));
imagePreview.setImageBitmap(myFilter.processFilter(finalImage.copy(Bitmap.Config.ARGB_8888, true)));
}
@Override
publicvoidonContrastChanged(finalfloatcontrast) {
contrastFinal = contrast;
Filter myFilter = newFilter();
myFilter.addSubFilter(newContrastSubFilter(contrast));
imagePreview.setImageBitmap(myFilter.processFilter(finalImage.copy(Bitmap.Config.ARGB_8888, true)));
}
@Override
publicvoidonEditStarted() {
}
@Override
publicvoidonEditCompleted() {
// once the editing is done i.e seekbar is drag is completed,
// apply the values on to filtered image
finalBitmap bitmap = filteredImage.copy(Bitmap.Config.ARGB_8888, true);
Filter myFilter = newFilter();
myFilter.addSubFilter(newBrightnessSubFilter(brightnessFinal));
myFilter.addSubFilter(newContrastSubFilter(contrastFinal));
myFilter.addSubFilter(newSaturationSubfilter(saturationFinal));
finalImage = myFilter.processFilter(bitmap);
}
/**
* Resets image edit controls to normal when new filter
* is selected
*/
privatevoidresetControls() {
if(editImageFragment != null) {
editImageFragment.resetControls();
}
brightnessFinal = 0;
saturationFinal = 1.0f;
contrastFinal = 1.0f;
}
classViewPagerAdapter extendsFragmentPagerAdapter {
privatefinalList mFragmentList = newArrayList<>();
privatefinalList mFragmentTitleList = newArrayList<>();
publicViewPagerAdapter(FragmentManager manager) {
super(manager);
}
@Override
publicFragment getItem(intposition) {
returnmFragmentList.get(position);
}
@Override
publicintgetCount() {
returnmFragmentList.size();
}
publicvoidaddFragment(Fragment fragment, String title) {
mFragmentList.add(fragment);
mFragmentTitleList.add(title);
}
@Override
publicCharSequence getPageTitle(intposition) {
returnmFragmentTitleList.get(position);
}
}
// load the default image from assets on app launch
privatevoidloadImage() {
originalImage = BitmapUtils.getBitmapFromAssets(this, IMAGE_NAME, 300, 300);
filteredImage = originalImage.copy(Bitmap.Config.ARGB_8888, true);
finalImage = originalImage.copy(Bitmap.Config.ARGB_8888, true);
imagePreview.setImageBitmap(originalImage);
}
@Override
publicbooleanonCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
returntrue;
}
@Override
publicbooleanonOptionsItemSelected(MenuItem item) {
intid = item.getItemId();
if(id == R.id.action_open) {
openImageFromGallery();
returntrue;
}
if(id == R.id.action_save) {
saveImageToGallery();
returntrue;
}
returnsuper.onOptionsItemSelected(item);
}
@Override
protectedvoidonActivityResult(intrequestCode, intresultCode, Intent data) {
if(resultCode == RESULT_OK && requestCode == SELECT_GALLERY_IMAGE) {
Bitmap bitmap = BitmapUtils.getBitmapFromGallery(this, data.getData(), 800, 800);
// clear bitmap memory
originalImage.recycle();
finalImage.recycle();
finalImage.recycle();
originalImage = bitmap.copy(Bitmap.Config.ARGB_8888, true);
filteredImage = originalImage.copy(Bitmap.Config.ARGB_8888, true);
finalImage = originalImage.copy(Bitmap.Config.ARGB_8888, true);
imagePreview.setImageBitmap(originalImage);
bitmap.recycle();
// render selected image thumbnails
filtersListFragment.prepareThumbnail(originalImage);
}
}
privatevoidopenImageFromGallery() {
Dexter.withActivity(this).withPermissions(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE)
.withListener(newMultiplePermissionsListener() {
@Override
publicvoidonPermissionsChecked(MultiplePermissionsReport report) {
if(report.areAllPermissionsGranted()) {
Intent intent = newIntent(Intent.ACTION_PICK);
intent.setType("image/*");
startActivityForResult(intent, SELECT_GALLERY_IMAGE);
} else{
Toast.makeText(getApplicationContext(), "Permissions are not granted!", Toast.LENGTH_SHORT).show();
}
}
@Override
publicvoidonPermissionRationaleShouldBeShown(List permissions, PermissionToken token) {
token.continuePermissionRequest();
}
}).check();
}
/*
* saves image to camera gallery
* */
privatevoidsaveImageToGallery() {
Dexter.withActivity(this).withPermissions(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE)
.withListener(newMultiplePermissionsListener() {
@Override
publicvoidonPermissionsChecked(MultiplePermissionsReport report) {
if(report.areAllPermissionsGranted()) {
finalString path = BitmapUtils.insertImage(getContentResolver(), finalImage, System.currentTimeMillis() + "_profile.jpg", null);
if(!TextUtils.isEmpty(path)) {
Snackbar snackbar = Snackbar
.make(coordinatorLayout, "Image saved to gallery!", Snackbar.LENGTH_LONG)
.setAction("OPEN", newView.OnClickListener() {
@Override
publicvoidonClick(View view) {
openImage(path);
}
});
snackbar.show();
} else{
Snackbar snackbar = Snackbar
.make(coordinatorLayout, "Unable to save image!", Snackbar.LENGTH_LONG);
snackbar.show();
}
} else{
Toast.makeText(getApplicationContext(), "Permissions are not granted!", Toast.LENGTH_SHORT).show();
}
}
@Override
publicvoidonPermissionRationaleShouldBeShown(List permissions, PermissionToken token) {
token.continuePermissionRequest();
}
}).check();
}
// opening image in default image viewer app
privatevoidopenImage(String path) {
Intent intent = newIntent();
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse(path), "image/*");
startActivity(intent);
}
}
运行应用程序并测试一次。您应该看到文章中显示的漂亮界面。您可以从列表中应用不同的滤镜,并可以控制亮度,饱和度和对比度。
已知的问题
1。当前图像控件更改图像控件时,亮度,对比度和饱和度不平滑。这是由于SeekBar值更改时递归处理/复制位图。改进必须在原生水平上完成。
2。过滤器不像Instagram那么好。在库的下一个版本中,我将尝试即兴创建过滤器。
3。过滤器不适合所有类型的图像。例如:具有白色背景的图像看起来与不同的滤镜几乎相似。