Maklumat

Tulisan-tulisan terkini dapat juga didapatkan di halaman Kompasiana di alamat https://kompasiana.com/didikaha

Khusus untuk konten-konten sastra seperti puisi, cerpen dan esai silahkan kunjungi http://blog.edelweis-art.com. Terima kasih (Penulis)

Selasa, September 11, 2018

Mengunggah Foto dari Ponsel dengan HTML5, jQuery dan PHP

Di jaman mobile sekarang ini, pengembang aplikasi berbasis web pun dituntut harus bisa menyesuaikan diri. Di mana tampilan dan fitur yang dibuat, harus mobile friendly juga. Salah satu fitur yang biasa digunakan adalah mengunggah foto. Dan untuk kasus mobile device, kamera menjadi hal utama yang harus dipertimbangkan.

Beruntung, jika kita mau rajin mencari dan membaca, sebenarnya di internet telah tersedia banyak artikel, contoh dan panduan untuk pengembangan aplikasi yang kita buat. Tinggal kita sesuaikan saja dengan kebutuhan dan kemampuan nalar kita. Karena, tidak selalu yang kita dapatkan cocok dan bisa kita terapkan untuk aplikasi kita. Seperti halnya tentang pengunggahan foto dari ponsel baik dari data yang sudah ada maupun mengambil langsung menggunakan kamera, dari sekian yang saya baca, saya menemukan satu artikel yang saya anggap cocok. Dan melalui tulisan ini, saya ingin berbagi dengan Anda yang sedang memerlukannya.

Secara umum, proses pengunggahan foto (baik dari perangkat selular maupun komputer biasa), dapat dibagi menjadi 3 bagian, yaitu:

  1. Memilih atau mengambil foto
  2. Memfilter dan mempreview foto
  3. Mengunggah foto

Untuk memilih atau mengambil foto, kita bisa menggunakan elemen input pada HTML5. Kenapa mesti HTML5? Ya, karena pada HTML5 telah dimungkinkan untuk menginput foto atau file lebih dari satu sekaligus. Sementara untuk memfilter foto (biasanya mengatur ulang ukuran atau dimensi foto yang terasa cukup besar) serta mempreviewnya, bisa dilakukan di sisi klien maupun server. Namun, saya lebih suka melakukannya di sisi klien. Toh, jika kemudian pengguna bermaksud membatalkan unggahannya, tidak perlu sampai merecoki server. Untuk memfilter dan mempreview foto di sisi klien, kita bisa menggunakan jQuery. Dan untuk hal terakhir, yaitu proses pengunggahan--dan penyimpanan--foto ke server, kita pun bisa menggunakan jQuery yang kemudian akan diterima dan diteruskan prosesnya menggunakan PHP.

Berikut impelementasinya:

file index.html
<div>
  <input id="upload-image" type="hidden" /><input accept="image/*" id="image-file" multiple="" type="file" /> <button id="upload-file">Upload</button>
</div>
<div id="preview-image"></div>

file upload.js
$(document).ready(function(){
 canvasLastID = 0;

 $(document).on("change", '#image-file', function(e) {
  var files = e.target.files;

  $.each(files, function(index,value){
   if(value) {
    if(/^image\//i.test(value.type)) {
     readFile(value,canvasLastID);
     canvasLastID++;
    } else {
     alert("Oops! Invalid file type");
    }
   }
  });

  $(this).val('');
 });

 $(document).on("click",".delete-canvas",function(){
  var index = $(this).attr('data-id');
  var dataURL = $(this).attr('data-content');

  var attachments = $("#upload-image").val();
  attachments = JSON.parse(attachments);

  var iO = attachments.indexOf(dataURL);
  attachments.splice(iO,1);

  var n = attachments.length;

  if(n == 0) {
   $("#upload-image").val('');
   $("#preview-image").empty();
   canvasLastID = 0;
  } else {
   attachments = JSON.stringify(attachments);
   $("#upload-image").val(attachments);
   $('.canvas-box[data-id='+index+']').remove();
  }
 });

 $(document).on("click","#upload-file",function(e){
  e.preventDefault();

  var data = {};
  data.imgs = $("#upload-image").val();

  if(data.imgs=="") {
   alert("Please select atleast one photo");
  } else {
   $.post("upload.php", data, function(data) {
    var data = JSON.parse(data);

    if(data.status==1) {
     $("#upload-image").val('');
     $("#preview-image").empty();
     canvasLastID = 0;
    }

    alert(data.message);
   });
  }

  return false;
 });
});

function readFile(file,index) {
 var reader = new FileReader();

 reader.onloadend = function () {
  processFile(reader.result, file.type, index);
 }

 reader.onerror = function () {
  alert("Oops! An error has occurred");
 }

 reader.readAsDataURL(file);
}

function processFile(dataURL, fileType, index) {
 var maxWidth = 800;
 var maxHeight = 800;

 var maxIconWidth = 150;
 var maxIconHeight = 150;

 var image = new Image();
 image.src = dataURL;

 image.onload = function () {
  var width = image.width;
  var height = image.height;

  if ((width > maxWidth) || (height > maxHeight)) {
   var newWidth;
   var newHeight;

   if (width > height) {
    newHeight = height * (maxWidth / width);
    newWidth = maxWidth;
   } else {
    newWidth = width * (maxHeight / height);
    newHeight = maxHeight;
   }

   var canvas = document.createElement('canvas');

   canvas.width = newWidth;
   canvas.height = newHeight;

   var context = canvas.getContext('2d');

   context.drawImage(this, 0, 0, newWidth, newHeight);

   dataURL = canvas.toDataURL(fileType);
  }

  // create icon images
  var newIconWidth;
  var newIconHeight;

  if ((width > maxIconWidth) || (height > maxIconHeight)) {
   if (width > height) {
    newIconHeight = height * (maxIconWidth / width);
    newIconWidth = maxIconWidth;
   } else {
    newIconWidth = width * (maxIconHeight / height);
    newIconHeight = maxIconHeight;
   }
  } else {
   newIconHeight = height;
   newIconWidth = width;
  }

  var canvas2 = document.createElement('canvas');
  canvas2.id = 'ImgIcon'+index;

  canvas2.width = newIconWidth;
  canvas2.height = newIconHeight;

  var context2 = canvas2.getContext('2d');

  context2.drawImage(this, 0, 0, newIconWidth, newIconHeight);

  // push dataURL to attachments
  var attachments = $("#upload-image").val();

  if(attachments=="") {
   attachments = [dataURL];
  } else {
   attachments = JSON.parse(attachments);
   attachments.push(dataURL);
  }

  attachments = JSON.stringify(attachments);

  $("#upload-image").val(attachments);

  // add icon image to preview image box
  $("#preview-image").append('<div class="canvas-box" data-id="'+index+'" style="width:'+newIconWidth+'px;height:'+newIconHeight+'px"></div>');
  $('.canvas-box[data-id='+index+']').append(canvas2);
  $('.canvas-box[data-id='+index+']').append('<span class="text-red delete-canvas" data-id="'+index+'" data-content="'+dataURL+'" style="position:absolute;top:4px;right:6px;z-index:99"><img src="icons/close.png" /></span>');
 };

 image.onerror = function () {
  alert("Oops! An error has occurred");
 };
}

file upload.php
$time = date('YmdHis');
$return = array();

function saveFromBase64($imageData,$newImage) {
 $imageData = explode('base64,', $imageData);
 $imageBase64 = $imageData[1];
 $imageString = base64_decode($imageBase64);
 $img = imagecreatefromstring($imageString);

 if($img !== false) {
  $path_parts = pathinfo($newImage);
  $extension = strtolower($path_parts['extension']);

  if($extension=='jpg' || $extension=='jpeg') {
   $save = imagejpeg($img, $newImage);
  } elseif($extension=='png') {
   $save = imagepng($img, $newImage);
  } else {
   $save = false;
  }

  imagedestroy($img);

  if($save) {
   return true;
  } else {
   return false;
  }
 } else {
  return false;
 }
}

if(isset($_POST['imgs']) && $_POST['imgs']!="") {
 $imgs = json_decode($_POST['imgs']);

 $count = 0;
 $success = 0;
 $failed = 0;

 $n = 0;

 foreach ($imgs as $img) {
  $n++;

  $filename = "images/img-$time-$n.png";
  $saved = saveFromBase64($img,$filename);

  if($saved) {
   $success++;
  } else {
   $failed++;
  }
 }

 $count = $n;

 if($count==$failed) {
  $return['status'] = 0;
  $return['message'] = "Oops! An error has occurred. No photos uploaded";
 } elseif($count==$success) {
  $return['status'] = 1;
  $return['message'] = "Yes! All photos uploaded";
 } else {
  $return['status'] = 2;
  $return['message'] = "$success of $count photos uploaded";
 }
} else {
 $return['status'] = 0;
 $return['message'] = "Invalid request";
}

echo json_encode($return);

Mula-mula, foto-foto yang akan diunggah, dicek dulu dimensinya. Dalam hal ini kita buat dimensi maksimal foto yang akan diunggah misalnya adalah width=800px dan height=800px. Artinya, jika dimensi foto melebihi ukuran tersebut, maka foto akan dikecilkan terlebih dahulu menggunakan canvas HTML5. Foto-foto tersebut lalu dikonversi menjadi data url yang akan dikirimkan ke server. Dan untuk memudahkan pengguna, sebelum foto-foto tersebut dikirim, kita buatkan preview-nya dalam ukuran-ukuran yang lebih kecil, misal width & height 150px menggunakan canvas HTML5. Di sini pengguna dapat menghapus juga foto-foto yang keliru dipilih. Setelah diterima di server, data url foto kemudian dikembalikan menjadi file image untuk disimpan.

Demikian, mudah-mudahan bermanfaat. Jika Anda membutuhkan source code-nya, silahkan klik di sini untuk mengunduhnya.