Memakai Efek Warna Di Kamera Pada Android


Bagi seorang engineer seperti saya, sebuah kamera hanyalah sebuah sensor yang membaca data cahaya. Namun bagi kalangan umum, kamera di perangkat Android sering dianggap sebagai alat yang penting. Tidak sedikit pengguna yang memilih perangkat Android berdasarkan kejernihan kameranya. Untuk memanfaatkan kamera secara maksimal, pengguna juga sering kali men-install aplikasi yang menawarkan efek menarik. Sebagai latihan, pada tulisan kali ini, saya akan mencoba membuat sebuah aplikasi yang mengambil gambar dari kamera dan memanfaatkan efek warna bawaan driver kamera.

Saya akan mulai dengan membuat sebuah proyek Android baru yang menggunakan API 19 (Android 4.4 KitKat). Pada saat membuat proyek, saya memilih Blank Activity with Fragment. Karena ingin menggunakan kamera dan menulis hasil rekaman kamera, saya perlu menambahkan izin penggunaan permission berikut ini pada AndroidManifest.xml:

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

Untuk melakukan preview kamera, saya akan membuat sebuah turunan SurfaceView baru seperti yang saya jumpai pada dokumentasi Android:

public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {

    private SurfaceHolder holder;
    private Camera camera;

    public CameraPreview(Context context, Camera camera) {
        super(context);
        this.camera = camera;
        holder = getHolder();
        holder.addCallback(this);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        try {
            camera.setPreviewDisplay(holder);
            camera.startPreview();
        } catch (IOException e) {
            Log.d("CameraPreview", "Error setting camera preview: " + e.getMessage());
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        if (this.holder.getSurface() == null) {
            return;
        }
        try {
            camera.stopPreview();
        } catch (Exception e) {}
        try {
            camera.setPreviewDisplay(this.holder);
            camera.startPreview();
        } catch (Exception e) {
            Log.d("CameraPreview", "Error starting camera preview: " + e.getMessage());
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {

    }
}

Setelah itu, saya menambahkan sebuah fragment baru dengan PreviewFragment.java yang isinya seperti berikut ini:

public class PreviewFragment extends Fragment {

    public PreviewFragment() {}

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        MainActivity activity = (MainActivity) getActivity();
        View view = inflater.inflate(R.layout.preview_fragment, container, false);
        FrameLayout framePreview = (FrameLayout) view.findViewById(R.id.preview);
        framePreview.addView(new CameraPreview(activity, activity.getCamera()));
        return view;
    }

}

PreviewFragment adalah sebuah fragment yang akan menampilkan preview kamera. Fragment ini membutuhkan sebuah android.hardware.Camera yang diperolehnya dengan memanggil method getCamera() dari MainActivity. Selain itu, saya juga perlu mendefinisikan layout yang dibutuhkan oleh fragment ini. Sebagai contoh, saya membuat preview_fragment.xml yang isinya berupa:

<RelativeLayout ...>

    <FrameLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:id="@+id/preview" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Efek Warna"
        android:id="@+id/btnEfekWarna"
        android:layout_gravity="left|top" />

</RelativeLayout>

Sekarang, saya akan mengubah kode program MainActivity agar menampilkan PreviewFragment, seperti:

public class MainActivity extends Activity {

    private Camera camera;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        camera = Camera.open();
        Camera.Parameters params = camera.getParameters();
        params.setJpegQuality(100);
        camera.setParameters(params);
        camera.setDisplayOrientation(90);

        getFragmentManager().beginTransaction()
            .add(R.id.container, new PreviewFragment())
            .commit();
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (camera != null) {
            camera.release();
        }
    }

    public Camera getCamera() {
        return camera;
    }

}

Google menambahkan API baru untuk mengakses kamera pada API 21 (Android 5.0 Lollipop). Selain itu, kode program yang mengakses kamera dengan android.hardware.Camera sudah di-deprecate pada API 21. Karena tidak memiliki perangkat Lollipop, saya akan tetap membuat kode program yang menggunakan android.hardware.Camera. Pada kode program di atas, saya mengakses kamera belakang dengan memanggil method open() dari android.hardware.Camera. Selain itu, agar latihan ini tetap sederhana, saya menggangap orientasi kamera selalu tegak (dengan setDisplayOrientation(90)). Tanpa kode program yang memanggil setDisplayOrientation() milik Camera sesuai dengan orientasi kamera, hasil preview akan ‘miring’ saat ditampilkan kamera berada dalam orientasi landscape.

Aplikasi kamera biasanya ditampilkan secara full-screen. Untuk itu, saya mengubah android:theme di AndroidManifest.xml menjadi seperti berikut ini:

android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"

Sekarang, bila saya menjalankan aplikasi, saya akan memperoleh hasil seperti pada gambar berikut ini:

Hasil preview kamera

Hasil preview kamera

Sampai disini, saya sudah bisa melihat preview kamera, akan tetapi saya masih belum bisa mengubah efek warna. Saya perlu membuat sebuah fragment baru dimana pengguna bisa memilih efek warna yang diinginkan. Untuk itu, saya membuat sebuah class baru dengan nama EfekWarnaFragment.java yang isinya seperti berikut ini:

public class EfekWarnaFragment extends Fragment implements AdapterView.OnItemClickListener {

    private Camera camera;
    private EfekWarnaAdapter efekWarnaAdapter;

    public EfekWarnaFragment() {}

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        MainActivity activity = (MainActivity) getActivity();
        camera = activity.getCamera();
        ListView daftarEfek = new ListView(activity);
        daftarEfek.setOnItemClickListener(this);
        TextView label = new TextView(activity);
        efekWarnaAdapter = new EfekWarnaAdapter(camera.getParameters().getSupportedColorEffects());
        daftarEfek.setAdapter(efekWarnaAdapter);
        return daftarEfek;
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        camera.stopPreview();
        Camera.Parameters params = camera.getParameters();
        params.setColorEffect((String) efekWarnaAdapter.getItem(position));
        camera.setParameters(params);
        getFragmentManager().popBackStack();
    }

    class EfekWarnaAdapter extends BaseAdapter {

        List<String> daftarEfek;

        public EfekWarnaAdapter(List<String> daftarEfek) {
            this.daftarEfek = daftarEfek;
        }

        @Override
        public int getCount() {
            return daftarEfek.size();
        }

        @Override
        public Object getItem(int position) {
            return daftarEfek.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            TextView view;
            if (convertView == null) {
                view = new TextView(getActivity());
                view.setGravity(Gravity.CENTER);
                view.setTextSize(20);
            } else {
                view = (TextView) convertView;
            }
            view.setText((String) getItem(position));
            return view;
        }

    }

}

Pada kode program di atas, saya menggunakan getSupportedColorEffects() milik Camera.Parameters untuk memperoleh sebuah List yang berisi daftar efek warna yang disediakan oleh driver kamera. Setelah itu, saya menampilkan daftar efek warna tersebut dalam sebuah ListView. Bila pengguna memilih salah satu efek warna, saya kemudian menggunakan setColorEffect() milik Camera.Parameters untuk mengaplikasikan efek warna tersebut pada kamera.

Kali ini, bila saya menjalankan aplikasi dan menyentuh tombol Efek Warna, saya akan memperoleh hasil seperti pada gambar berikut ini:

Daftar efek warna yang didukung oleh driver kamera

Daftar efek warna yang didukung oleh driver kamera

Bila saya memilih salah satu efek warna, misalnya negative, saya akan memperoleh hasil seperti pada gambar berikut ini:

Hasil preview kamera setelah memilih efek negative

Hasil preview kamera setelah memilih efek negative

Sebagai langkah terakhir, saya akan menambahkan kode program yang menyimpan file gambar bila layar preview disentuh. Untuk itu, saya menambahkan OnTouchListener pada CameraPreview seperti:

public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback, View.OnTouchListener {

  ...

  public CameraPreview(Context context, Camera camera) {
    ...
    setOnTouchListener(this);
  }

  ...

  @Override
  public boolean onTouch(View v, MotionEvent event) {
    Camera.PictureCallback pictureCallback = new Camera.PictureCallback() {
      @Override
      public void onPictureTaken(byte[] data, Camera camera) {
        File picDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "MyCam");
        if (!picDir.exists()) {
          picDir.mkdirs();
        }
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        try {
          File outputFile = new File(picDir, "IMG_" + timeStamp + ".jpg");
          FileOutputStream fos = new FileOutputStream(outputFile);
          fos.write(data);
          fos.close();
          MediaScannerConnection.scanFile(getContext(), new String[]{outputFile.getPath()}, new String[]{"image/jpeg"}, null);
          camera.startPreview();
        } catch (IOException e) {
          Log.e("MyCam", "Terjadi kesalahan saat menulis file gambar", e);
        }
      }
    };
    camera.takePicture(null, null, pictureCallback);
    return false;
  }

}

Untuk menyimpan hasil preview di kamera ke dalam sebuah file JPEG, saya memanggil method takePicture() milik Camera. Pada method ini, saya melewatkan sebuah Camera.PictureCallback yang akan dikerjakan saat file gambar telah diolah oleh driver kamera. onPictureTaken() mengandung byte[] yang merupakan sebuah gambar dalam format JPEG. Yang perlu saya lakukan adalah menulis file ini ke direktori penyimpanan gambar. Selain itu, saya juga perlu memanggil method scanFile() milik MediaScannerConnection agar file gambar baru ini dapat langsung dilihat melalui aplikasi lain.

Sekarang, saya bisa membuat file gambar dari kamera dengan menyentuh preview kamera. Walaupun demikian, rotasi pada preview kamera berbeda dengan yang ada di file JPEG yang dihasilkan. Ternyata camera.setDisplayOrientation(90) hanya memiliki efek pada preview, tetapi tidak berpengaruh pada gambar yang dihasilkan. Oleh sebab itu, saya menambahkan setRotation() milik Camera.Parameters sehingga method onCreate() pada MainActivity terlihat seperti berikut ini:

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  camera = Camera.open();
  Camera.Parameters params = camera.getParameters();
  params.setJpegQuality(100);
  params.setRotation(90);
  camera.setParameters(params);
  camera.setDisplayOrientation(90);

  getFragmentManager().beginTransaction()
    .add(R.id.container, new PreviewFragment())
    .commit()
}

Sekarang, hasil file gambar yang diperoleh akan memiliki orientasi yang sama dengan orientasi di preview (sama-sama portrait).

Perihal Solid Snake
I'm nothing...

One Response to Memakai Efek Warna Di Kamera Pada Android

  1. Ping-balik: Mengatur Wilayah Focus Pada Kamera Di Android | The Solid Snake

Apa komentar Anda?

Please log in using one of these methods to post your comment:

Logo WordPress.com

You are commenting using your WordPress.com account. Logout / Ubah )

Gambar Twitter

You are commenting using your Twitter account. Logout / Ubah )

Foto Facebook

You are commenting using your Facebook account. Logout / Ubah )

Foto Google+

You are commenting using your Google+ account. Logout / Ubah )

Connecting to %s

%d blogger menyukai ini: