Hướng dẫn tạo bộ lọc màu ảnh giống như trong Instagram phần 2
This post hasn't been updated for 3 years
Bài viết lần này sẽ tiếp tục hướng dẫn các bạn viết ứng dụng lọc ảnh, các bạn có thể xem lại phần 1 bài viết này tại đây
5. Tạo RecyclerView Adapter cho Thumbnails filter
Ở phàn 1 chúng ta đã có tất cả các class bắt buộc. Bây giờ hãy cùng tạo class adapter của RecyclerView trước khi chuyển sang giao diện người dùng thực tế.
Bước 10
Tạo một file layout mới có tên thumbnail_list_item.xml. Layout này chứa một TextView và ImageView để hiển thị tên bộ lọc và hình thumbnail.
thumbnail_list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
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" />
<ImageView
android:id="@+id/thumbnail"
android:layout_width="@dimen/thumbnail_size"
android:layout_height="@dimen/thumbnail_size"
android:src="@mipmap/ic_launcher" />
</LinearLayout>
Bước 11
Tạo một class tên là ThumbnailsAdapter.java, class này hoạt động như bộ thu nhỏ của RecyclerView để hiển thị hình ảnh được lọc trong danh sách ngang.
ThumbnailsAdapter.java
package info.androidhive.imagefilters;
import android.content.Context;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.zomato.photofilters.imageprocessors.Filter;
import com.zomato.photofilters.utils.ThumbnailItem;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
/**
* Created by ravi on 23/10/17.
*/
public class ThumbnailsAdapter extends RecyclerView.Adapter<ThumbnailsAdapter.MyViewHolder> {
private List<ThumbnailItem> thumbnailItemList;
private ThumbnailsAdapterListener listener;
private Context mContext;
private int selectedIndex = 0;
public class MyViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.thumbnail)
ImageView thumbnail;
@BindView(R.id.filter_name)
TextView filterName;
public MyViewHolder(View view) {
super(view);
ButterKnife.bind(this, view);
}
}
public ThumbnailsAdapter(Context context, List<ThumbnailItem> thumbnailItemList, ThumbnailsAdapterListener listener) {
mContext = context;
this.thumbnailItemList = thumbnailItemList;
this.listener = listener;
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext())
.inflate(R.layout.thumbnail_list_item, parent, false);
return new MyViewHolder(itemView);
}
@Override
public void onBindViewHolder(MyViewHolder holder, final int position) {
final ThumbnailItem thumbnailItem = thumbnailItemList.get(position);
holder.thumbnail.setImageBitmap(thumbnailItem.image);
holder.thumbnail.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(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
public int getItemCount() {
return thumbnailItemList.size();
}
public interface ThumbnailsAdapterListener {
void onFilterSelected(Filter filter);
}
}
6. Thêm danh sách các bộ lọc ảnh trong list Fragment
Bây giờ chúng ta sẽ tạo một class Fragment để hiển thị hìnhthumbnails của bức ảnh được lọc trong danh sách nằm ngang. Để đạt được điều này, chúng ta cần một RecyclerView và cung cấp danh sách các hình ảnh thu nhỏ cho class adapter.
Bước 12
Tạo một Fragment mới bằng cách vào File ⇒ New ⇒ Fragment ⇒ Fragment (Blank) và đặt tên nó là FiltersListFragment.java.
Bước 13
Mở file layout của fragment_filters_list.xml fragment và thêm phần tử RecyclerView.
fragment_filters_list.xml
<FrameLayout xmlns:android="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.support.v7.widget.RecyclerView
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" />
</FrameLayout>
Bước 14
Mở file FiltersListFragment.java và thực hiện các sửa đổi như được hiển thị bên dưới.
FilterPack.getFilterPack () cung cấp danh sách các bộ lọc có sẵn từ thư viện.
Trong phương thức prepareThumbnail () lọc qua và mỗi thumbnail item được thêm vào ThumbnailsManager để xử lý chúng. Các hình thumbnail được xử lý được thêm vào lại vào thumbnailItemList là tài nguyên dữ liệu cho RecyclerView.
Khi bộ dữ liệu hình thu nhỏ đã sẵn sàng, mAdapter.notifyDataSetChanged () được gọi để hiển thị danh sách. Tất cả điều này đã được thực hiện trong background thread như quá trình xử lý hình ảnh
Interfac FiltersListFragmentListener cung cấp phương thức gọi lại chomain activity bất cứ khi nào một bộ lọc mới được chọn.
Việc xử lý thực tế bộ lọc hình ảnh đã chọn sẽ được nói cụ thể khi nói về MainActivity.
FiltersListFragment.java
package info.androidhive.imagefilters;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.zomato.photofilters.FilterPack;
import com.zomato.photofilters.imageprocessors.Filter;
import com.zomato.photofilters.utils.ThumbnailItem;
import com.zomato.photofilters.utils.ThumbnailsManager;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import butterknife.BindView;
import butterknife.ButterKnife;
import info.androidhive.imagefilters.utils.BitmapUtils;
import info.androidhive.imagefilters.utils.SpacesItemDecoration;
public class FiltersListFragment extends Fragment implements ThumbnailsAdapter.ThumbnailsAdapterListener {
@BindView(R.id.recycler_view)
RecyclerView recyclerView;
ThumbnailsAdapter mAdapter;
List<ThumbnailItem> thumbnailItemList;
FiltersListFragmentListener listener;
public void setListener(FiltersListFragmentListener listener) {
this.listener = listener;
}
public FiltersListFragment() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View 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 = new ArrayList<>();
mAdapter = new ThumbnailsAdapter(getActivity(), thumbnailItemList, this);
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false);
recyclerView.setLayoutManager(mLayoutManager);
recyclerView.setItemAnimator(new DefaultItemAnimator());
int space = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8,
getResources().getDisplayMetrics());
recyclerView.addItemDecoration(new SpacesItemDecoration(space));
recyclerView.setAdapter(mAdapter);
prepareThumbnail(null);
return view;
}
/**
* Renders thumbnails in horizontal list
* loads default image from Assets if passed param is null
*
* @param bitmap
*/
public void prepareThumbnail(final Bitmap bitmap) {
Runnable r = new Runnable() {
public void run() {
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 = new ThumbnailItem();
thumbnailItem.image = thumbImage;
thumbnailItem.filterName = getString(R.string.filter_normal);
ThumbnailsManager.addThumb(thumbnailItem);
List<Filter> filters = FilterPack.getFilterPack(getActivity());
for (Filter filter : filters) {
ThumbnailItem tI = new ThumbnailItem();
tI.image = thumbImage;
tI.filter = filter;
tI.filterName = filter.getName();
ThumbnailsManager.addThumb(tI);
}
thumbnailItemList.addAll(ThumbnailsManager.processThumbs(getActivity()));
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
mAdapter.notifyDataSetChanged();
}
});
}
};
new Thread(r).start();
}
@Override
public void onFilterSelected(Filter filter) {
if (listener != null)
listener.onFilterSelected(filter);
}
public interface FiltersListFragmentListener {
void onFilterSelected(Filter filter);
}
}
7. Thêm fragment chỉnh sửa, controls ảnh
Bây giờ chúng ta sẽ thêm phần controls ảnh để điều khiển độ sáng, độ tương phản và độ bão hòa.
Bước 15
Tạo thêm một fragment có tên EditImageFragment.java bằng cách thực hiện theo các bước. Mở file layout của fragment có tên là fragment_edit_image.xml này và thực hiện các sửa đổi như dưới đây.
Trong layoyt này chúng ta sẽ thêm ba view: SeekBar để điều khiển độ sáng, độ tương phản và độ bão hòa của hình ảnh.
fragment_edit_image.xml
<LinearLayout xmlns:android="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">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingBottom="@dimen/padding_10"
android:paddingTop="@dimen/padding_10">
<TextView
android:layout_width="@dimen/lbl_edit_image_control"
android:layout_height="wrap_content"
android:text="@string/lbl_brightness" />
<SeekBar
android:id="@+id/seekbar_brightness"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingBottom="@dimen/padding_10"
android:paddingTop="@dimen/padding_10">
<TextView
android:layout_width="@dimen/lbl_edit_image_control"
android:layout_height="wrap_content"
android:text="@string/lbl_contrast" />
<SeekBar
android:id="@+id/seekbar_contrast"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingBottom="@dimen/padding_10"
android:paddingTop="@dimen/padding_10">
<TextView
android:layout_width="@dimen/lbl_edit_image_control"
android:layout_height="wrap_content"
android:text="@string/lbl_saturation" />
<SeekBar
android:id="@+id/seekbar_saturation"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
</LinearLayout>
</LinearLayout>
Bước 16
Mở EditImageFragment.java và thực hiện các sửa đổi như dưới đây.
Trong phương thức onCreateView, các widgets Seekbar được khởi tạo với giá trị ban đầu và giá trị cực đại. Đối với độ sáng, giá trị có thể từ -100 / 100. Độ tương phản và độ bão hòa có giá trị nổi.
Interface EditImageFragmentListener cung cấp các phương thức gọi lại bất cứ khi nào khi các giá trị Seekbar thay đổi.
Việc xử lý độ sáng, độ tương phản và độ bão hòa một lần nữa được nói cụ thể trong MainActivity khi gọi lại.
EditImageFragment.java
package info.androidhive.imagefilters;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.SeekBar;
import butterknife.BindView;
import butterknife.ButterKnife;
public class EditImageFragment extends Fragment implements SeekBar.OnSeekBarChangeListener {
private EditImageFragmentListener listener;
@BindView(R.id.seekbar_brightness)
SeekBar seekBarBrightness;
@BindView(R.id.seekbar_contrast)
SeekBar seekBarContrast;
@BindView(R.id.seekbar_saturation)
SeekBar seekBarSaturation;
public void setListener(EditImageFragmentListener listener) {
this.listener = listener;
}
public EditImageFragment() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View 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);
return view;
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean b) {
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;
float floatVal = .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
float floatVal = .10f * progress;
listener.onSaturationChanged(floatVal);
}
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
if (listener != null)
listener.onEditStarted();
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
if (listener != null)
listener.onEditCompleted();
}
public void resetControls() {
seekBarBrightness.setProgress(100);
seekBarContrast.setProgress(0);
seekBarSaturation.setProgress(10);
}
public interface EditImageFragmentListener {
void onBrightnessChanged(int brightness);
void onSaturationChanged(float saturation);
void onContrastChanged(float contrast);
void onEditStarted();
void onEditCompleted();
}
}
8. Thực hiện giao diện chính (kết hợp các fragment)
Bây giờ chúng ta đã có các fragments , chúng ta hãy cùng xem cách kết hợp chúng để đạt được kết quả cuối cùng
Bước 17
Mở file layout activity_main.xml và content_main.xml và NonSwipeableViewPager và TabLayout và thêm đoạn code sau
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="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.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@android:color/white"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<include layout="@layout/content_main" />
</android.support.design.widget.CoordinatorLayout>
content_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="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">
<ImageView
android:id="@+id/image_preview"
android:layout_width="match_parent"
android:layout_height="360dp"
android:scaleType="centerCrop" />
<info.androidhive.imagefilters.utils.NonSwipeableViewPager
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.support.design.widget.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
app:tabGravity="fill"
app:tabMode="fixed" />
</RelativeLayout>
Bước 18
Mở file MainActivity.java và thực hiện các thay đổi như dưới đây.
System.loadLibrary ("NativeImageProcessor") được gọi để khởi tạo thư viện gốc.
FiltersListFragment và EditImageFragments được thêm vào ViewPager trong phương thức setupViewPager ().
onFilterSelected () sẽ được gọi khi bộ lọc được chọn trong FiltersListFragment. Bộ lọc đã chọn được xử lý và hình ảnh cuối cùng được hiển thị imagePreview.
onBrightnessChanged (), onSaturationChanged () và onContrastChanged () sẽ được gọi khi các giá trị Seekbar thay đổi trong EditImageFragments.
Hình ảnh cuối cùng sẽ được lưu vào Thư viện khi lựa chọn SAVE từ Thanh công cụ.
MainActivity.java
package info.androidhive.imagefilters;
import android.Manifest;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.Snackbar;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;
import com.karumi.dexter.Dexter;
import com.karumi.dexter.MultiplePermissionsReport;
import com.karumi.dexter.PermissionToken;
import com.karumi.dexter.listener.PermissionRequest;
import com.karumi.dexter.listener.multi.MultiplePermissionsListener;
import com.zomato.photofilters.imageprocessors.Filter;
import com.zomato.photofilters.imageprocessors.subfilters.BrightnessSubFilter;
import com.zomato.photofilters.imageprocessors.subfilters.ContrastSubFilter;
import com.zomato.photofilters.imageprocessors.subfilters.SaturationSubfilter;
import java.util.ArrayList;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import info.androidhive.imagefilters.utils.BitmapUtils;
public class MainActivity extends AppCompatActivity implements FiltersListFragment.FiltersListFragmentListener, EditImageFragment.EditImageFragmentListener {
private static final String TAG = MainActivity.class.getSimpleName();
public static final String IMAGE_NAME = "dog.jpg";
public static final int SELECT_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
int brightnessFinal = 0;
float saturationFinal = 1.0f;
float contrastFinal = 1.0f;
// load native image filters library
static {
System.loadLibrary("NativeImageProcessor");
}
@Override
protected void onCreate(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);
}
private void setupViewPager(ViewPager viewPager) {
ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager());
// adding filter list fragment
filtersListFragment = new FiltersListFragment();
filtersListFragment.setListener(this);
// adding edit image fragment
editImageFragment = new EditImageFragment();
editImageFragment.setListener(this);
adapter.addFragment(filtersListFragment, getString(R.string.tab_filters));
adapter.addFragment(editImageFragment, getString(R.string.tab_edit));
viewPager.setAdapter(adapter);
}
@Override
public void onFilterSelected(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
public void onBrightnessChanged(final int brightness) {
brightnessFinal = brightness;
Filter myFilter = new Filter();
myFilter.addSubFilter(new BrightnessSubFilter(brightness));
imagePreview.setImageBitmap(myFilter.processFilter(finalImage.copy(Bitmap.Config.ARGB_8888, true)));
}
@Override
public void onSaturationChanged(final float saturation) {
saturationFinal = saturation;
Filter myFilter = new Filter();
myFilter.addSubFilter(new SaturationSubfilter(saturation));
imagePreview.setImageBitmap(myFilter.processFilter(finalImage.copy(Bitmap.Config.ARGB_8888, true)));
}
@Override
public void onContrastChanged(final float contrast) {
contrastFinal = contrast;
Filter myFilter = new Filter();
myFilter.addSubFilter(new ContrastSubFilter(contrast));
imagePreview.setImageBitmap(myFilter.processFilter(finalImage.copy(Bitmap.Config.ARGB_8888, true)));
}
@Override
public void onEditStarted() {
}
@Override
public void onEditCompleted() {
// once the editing is done i.e seekbar is drag is completed,
// apply the values on to filtered image
final Bitmap bitmap = filteredImage.copy(Bitmap.Config.ARGB_8888, true);
Filter myFilter = new Filter();
myFilter.addSubFilter(new BrightnessSubFilter(brightnessFinal));
myFilter.addSubFilter(new ContrastSubFilter(contrastFinal));
myFilter.addSubFilter(new SaturationSubfilter(saturationFinal));
finalImage = myFilter.processFilter(bitmap);
}
/**
* Resets image edit controls to normal when new filter
* is selected
*/
private void resetControls() {
if (editImageFragment != null) {
editImageFragment.resetControls();
}
brightnessFinal = 0;
saturationFinal = 1.0f;
contrastFinal = 1.0f;
}
class ViewPagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> mFragmentList = new ArrayList<>();
private final List<String> mFragmentTitleList = new ArrayList<>();
public ViewPagerAdapter(FragmentManager manager) {
super(manager);
}
@Override
public Fragment getItem(int position) {
return mFragmentList.get(position);
}
@Override
public int getCount() {
return mFragmentList.size();
}
public void addFragment(Fragment fragment, String title) {
mFragmentList.add(fragment);
mFragmentTitleList.add(title);
}
@Override
public CharSequence getPageTitle(int position) {
return mFragmentTitleList.get(position);
}
}
// load the default image from assets on app launch
private void loadImage() {
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
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_open) {
openImageFromGallery();
return true;
}
if (id == R.id.action_save) {
saveImageToGallery();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, 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);
}
}
private void openImageFromGallery() {
Dexter.withActivity(this).withPermissions(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE)
.withListener(new MultiplePermissionsListener() {
@Override
public void onPermissionsChecked(MultiplePermissionsReport report) {
if (report.areAllPermissionsGranted()) {
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType("image/*");
startActivityForResult(intent, SELECT_GALLERY_IMAGE);
} else {
Toast.makeText(getApplicationContext(), "Permissions are not granted!", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onPermissionRationaleShouldBeShown(List<PermissionRequest> permissions, PermissionToken token) {
token.continuePermissionRequest();
}
}).check();
}
/*
* saves image to camera gallery
* */
private void saveImageToGallery() {
Dexter.withActivity(this).withPermissions(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE)
.withListener(new MultiplePermissionsListener() {
@Override
public void onPermissionsChecked(MultiplePermissionsReport report) {
if (report.areAllPermissionsGranted()) {
final String 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", new View.OnClickListener() {
@Override
public void onClick(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
public void onPermissionRationaleShouldBeShown(List<PermissionRequest> permissions, PermissionToken token) {
token.continuePermissionRequest();
}
}).check();
}
// opening image in default image viewer app
private void openImage(String path) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse(path), "image/*");
startActivity(intent);
}
}
Chạy ứng dụng và thử nghiệm một lần. Bạn sẽ thấy giao diện đẹp như trong bài báo. Bạn có thể áp dụng các bộ lọc khác nhau từ danh sách và có thể kiểm soát độ sáng, độ bão hòa và độ tương phản.
All Rights Reserved