Pada artikel ini Saya akan menjelaskan algoritma perhitungan dimensi untuk melakukan resize gambar secara proporsional.

PHP Saya pilih untuk artikel ini dikarenakan sering sekali website memerlukan untuk melakukan resize gambar baik itu untuk keperluan bandwith dan speed, maupun untuk keperluan estetika, dan php salah satu yang paling populer dan paling mudah dideploy.

Tapi bukan berarti algoritma ini hanya berguna untuk php, karena yang akan lebih dijelaskan pada artikel ini adalah algoritma dan cara-cara melakukannya.

Jenis-jenis resize proportional

Ada beberapa jenis resize proportional, diantaranya cover, fit atau skala.

Untuk cover dan fit Kita dapat menetapkan target width dan height nya secara pasti karena fit akan meresize gambar sampai salah satu sisinya (vertikal atau horizontal) menyentuh batas, dan cover akan meresizenya sampai tidak ada ruang yang kosong pada dimensi yang diminta (bila ada sisi yang belum menyentuh batas, gambar akan diperbesar).

Sedangkan untuk skala, Kita hanya bisa menentukan skala nya, atau ukuran salah satu sisi width atau heightnya. Bila yang ditentukan width, maka height nya akan mengikuti rasio height dari sumbernya, begitu juga sebaliknya, bila yang ditentukan adalah Height, maka width yang mengikuti rasio sumber.

Menghitung Rasio

Rasio bisa dengan mudah Kita hitung, yaitu dengan membagi nilai target dengan nilai sumber.

Rasio = Target / Sumber

Misalnya Kita memiliki gambar dengan width=1000 dan ingin resize menjadi 500, maka rasio dari resize itu 0.5.

Rasio = 500 / 1000
Rasio = 0.5

Dengan rasio ini, Kita dapat menghitung sisi lainnya, misalnya untuk jenis resize skala, Kita ingin tahu target height menjadi berapa, maka yang perlu dilakukan adalah mengkalikan rasio yang telah dihitung dengan height sumber.

Target = Rasio * Sumber

Maka pada contoh di atas, bila Kita sudah mendapatkan rasio yang dihitung dari width, dan ingin mencari target height, maka Kita harus mengalikan rasio dengan height sumber, misalnya height sumber adalah 600, maka:

TargetHeight = 0.5 * 600
TargetHeight = 300

Pada contoh di atas, dimana sumber gambar memiliki ukuran 1000×600 dan dilakukan resize dengan rasio 0.5 dan menghasilkan ukuran target 500×300 merupakan resize yang proportional, karena ukuran target tidak akan menyebabkan gambar terdistorsi, seperti pada gambar di bawah ini:

Skala atau Satu Sisi

Dengan penjelasan rasio di atas, sebenarnya semua perhitungan sudah dijelaskan, karena bila Kita sudah memiliki ukuran target dan koordinat awal pasti di x=0 dan y=0 maka Kita telah memiliki semua komponen untuk resize secara skala atau satu sisi.

Yang mungkin perlu dilihat lagi adalah parameter apa yang Kita dapatkan untuk resize ini.

Ada tiga parameter yang bisa digunakan untuk resize ini, dan hanya dipilih salah satu dari tiga berikut:

  1. Rasio Skala. Bila paramenter berupa rasio, maka width dan height ukurannya akan dihitung sesuai rasio.
  2. Target Width. Bila parameter berupa width, maka height akan mengikuti rasio dari width.
  3. Target Height. Bila parameter berupa height, maka width akan mengikuti rasio dari height.

Bila parameter yang didapat adalah Rasio skala, maka:

TargetWidth = Rasio * WidthSumber
TargetHeight = Rasio * HeightSumber

Bila parameter berupa Target Width, maka:

Rasio = TargetWidth / WidthSumber
TargetHeight = Rasio * HeightSumber

Dan bila parameter berupa Target Height, maka:

Rasio = TargetHeight / HeightSumber
TargetWidth = Rasio * WidthSumber

Dengan ini, Kita sudah memiliki komponen-komponen yang diperlukan untuk resize:

WidthSumber
HeightSumber
Rasio
TargetWidth
TargetHeight
TargetX = 0
TargetY = 0
SumberX = 0
SumberY = 0

Maka yang harus dilakukan tinggal memanggil fungsi resize:

Resize(
  TargetX, TargetY, SumberX, SumberY,
  TargetWidth, TargetHeight, WidthSumber, HeightSumber
)

Tentunya fungsi resize di atas tergantung dengan bahasa pemrograman yang digunakan.

Fit dan Cover

Untuk fit dan cover, hal yang sama dengan skala/satu sisi hanya dalam perhitungan rasio, karena selain menghitung rasio dan ukuran target, pada resize fit dan cover juga harus melakukan komparasi rasio yang lebih besar/kecil dan koordinat targetnya.

Langkah pertama yang harus dilakukan adalah menghitung kedua rasio width dan height. Dikarenakan parameter yang didapatkan adalah TargetWidth dan TargetHeight, maka untuk menghitung rasio keduanya adalah:

RasioWidth = TargetWidth / WidthSumber
RasioHeight = TargetHeight / HeightSumber

Untuk fit yang akan digunakan adalah rasio yang lebih kecil, sedangkan untuk cover yang digunakan adalah rasio yang lebih besar, maka diperlukan seleksi kondisi seperti berikut:

Rasio = RasioWidth

If fit and RasioHeight < RasioWidth
  Rasio = RasioHeight

Else If cover and RasioHeight > RasioWidth
  Rasio = RasioHeight

Pada logic di atas, Kita defaulkan rasio awal menggunakan rasio dari width, tapi bila jenis resizenya fit dan rasio height lebih kecil dari rasio width, maka rasio height yang akan digunakan sebagai rasio yang akan digunakan, begitu juga bila jenisnya cover dan rasio height lebih besar dari rasio width, maka rasio height yang akan digunakan sebagai rasio yang akan digunakan.

Ya… Selain defaultkan rasio menjadi rasio dari width, Rasio = RasioWidth juga bisa disimpan pada else, ini hanya preferensi saja, karena terbiasa (dan best practice sebenarnya) variabel didefinisikan diluar seleksi kondisi terlebih dahulu.

Setelah mengetahui rasionya, maka Kita harus menghitung target resize untuk width dan height. Target resize ini berbeda dengan target ukuran akhir gambar, karena kemungkinan satu sisi dari gambar yang diresize tidak sama ukurannya dengan ukuran target yang diinginkan. Cara menghitungnya adalah:

ResizeWidth = Rasio * WidthSumber
ResizeHeight = Rasio * HeightSumber

Hal terakhir yang perlu dihitung adalah posisi/koordinat target, agar posisi yang gambar yang ditampilkan berada di tengah. Kecuali bila ingin posisi gambar berada di sisi kiri atas (Bisa gunakan TargetX dan TargetY = 0).

Untuk menghitung koordinat target, yang perlu dilakukan adalah mengurangi nilai Target dengan nilai Resize lalu dibagi dua, seperti berikut ini:

TargetX = (TargetWidth - ResizeWidth) / 2
TargetY = (TargetHeight - ResizeHeight) / 2

Kenapa dibagi dua?. Mari Kita terlebih dahulu. Ketika TargetWidth dikurangi ResizeWidth, yang didapatkan adalah ukuran sisanya. Bila tidak dibagi dua, maka posisi gambar akan berada di sebalah kanan atau bawah gambar hasil, dikarenakan Kita memposisikan gambarnya si sisa pengurangan. Silahkan pahami lihat bagan berikut:

Maka Kita telah memiliki semua parameter yang diperlukan untuk melakukan resize, yaitu:

WidthSumber
HeightSumber
Rasio
TargetWidth
TargetHeight
ResizeWidth
ResizeHeight
TargetX
TargetY
SumberX = 0
SumberY = 0

Tapi sebelum melakukan resize, untuk jenis fit, Kita harus menggambar persegi (rectangle) terlebih dahulu, karena posisi gambar kemungkinan berada di dalam ukuran target, dan kemungkinan memiliki bagian yang tidak di dalam bagian gambar. Setelah itu baru Kita dapat memanggil fungsi resize.

If fit
  Rectangle ( 0, 0, TargetWidth, TargetHeight, Hitam )

Resize(
  TargetX, TargetY, SumberX, SumberY,
  ResizeWidth, ResizeHeight, WidthSumber, HeightSumber
)

Bila mendukung fungsi fill target, maka Kita dapat menggunakannya, karena tujuan yang diinginkan sama:

If fit
  Fill ( Hitam )

Resize(
  TargetX, TargetY, SumberX, SumberY,
  ResizeWidth, ResizeHeight, WidthSumber, HeightSumber
)

Catatan: Warna yang dipilih untuk fill/rectangle dapat disesuaikan sesuai keinginan.

Berikut adalah contoh perbedaan jenis fit dan cover:

Perbedaan Ukuran Resize dan Target

Perbedaannya adalah bila Ukuran Target digunakan untuk ukuran gambar yang akan dihasilkan, sedangkan Ukuran Resize adalah ukuran konten gambar sumber yang akan dibesar/kecilkan.

Maka Ukuran Target akan digunakan ketika membuat tempat akhir gambar, sedangkan Ukuran Resize digukanakan ketika memanggil fungsi resize. Silahkan lihat algoritma lengkap dari fungsi resize fit berikut di bawah ini:

Function ResizeFit ( ImageSumber, TargetWidth, TargetHeight )
  # Ambil Ukuran Sumber
  WidthSumber = GetWidth( ImageSumber )
  HeightSumber = GetWidth( ImageSumber )

  # Hitung Rasio
  RasioWidth = TargetWidth / WidthSumber
  RasioHeight = TargetHeight / HeightSumber

  # Tentukan rasio yang digunakan
  Rasio = RasioWidth
  If RasioHeight < RasioWidth
    Rasio = RasioHeight

  # Hitung ukuran resize
  ResizeWidth = Rasio * WidthSumber
  ResizeHeight = Rasio * HeightSumber

  # Hitung koordinat target
  TargetX = (TargetWidth - ResizeWidth) / 2
  TargetY = (TargetHeight - ResizeHeight) / 2

  # Buat Image Target
  ImageTarget = NewImage ( TargetWidth, TargetHeight )

  # Pastikan fill target terlebih dahulu untuk fit
  Fill ( ImageTarget, BLACK )

  # Gambar dan resize image sumber ke image target
  Resize(
    ImageTarget, ImageSumber,
    TargetX, TargetY, SumberX, SumberY,
    ResizeWidth, ResizeHeight, WidthSumber, HeightSumber
  )

  # Return Image Target
  return ImageTarget
End

Implementasi pada PHP

Dengan algoritma yang telah dijelaskan di atas, maka Saya buatkan implementasi class pada php seperti berikut ini:

<?php
/*
 * Copyright 2022 Ahmad Amarullah (https://amarullz.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

/**
 * Class untuk melakukan resize image secara proporsional
 */
class PropResize
{
  /* Scale Type */
  public const ScaleWidth = 1;
  public const ScaleHeight = 2;
  public const Fit = 3;
  public const Cover = 4;

  /**
   * Resize image secara proportional dari image resource
   * 
   * @param GDImage $ImageSumber Image resource sumber
   * @param int $TargetWidth Target width (diacuhkan bila tipe=ScaleHeight)
   * @param int $TargetHeight Target height (diacuhkan bila tipe=ScaleWidth)
   * @param int $Type tipe resize yang akan dilakukan
   * @return GDImage Image resource hasil resize
   */
  public static function resize($ImageSumber, $TargetWidth, $TargetHeight, $Type)
  {
    // Ambil ukuran sumber
    $WidthSumber = imagesx($ImageSumber);
    $HeightSumber = imagesy($ImageSumber);

    // Hitung rasio
    $RasioWidth = $TargetWidth / $WidthSumber;
    $RasioHeight = $TargetHeight / $HeightSumber;

    // Tentukan rasio yang digunakan
    $Rasio = $RasioWidth;
    if (($Type == self::ScaleHeight) ||
      (($Type == self::Fit) && ($RasioHeight < $RasioWidth)) ||
      (($Type == self::Cover) && ($RasioHeight > $RasioWidth))
    )
      $Rasio = $RasioHeight;

    // Hitung ukuran resize
    $ResizeWidth = $Rasio * $WidthSumber;
    $ResizeHeight = $Rasio * $HeightSumber;

    // Siapkan koordinat target
    $TargetX = 0;
    $TargetY = 0;

    if ($Type == self::Fit || $Type == self::Cover) {
      // Hitung koordinat target untuk fit dan cover
      $TargetX = ($TargetWidth - $ResizeWidth) / 2;
      $TargetY = ($TargetHeight - $ResizeHeight) / 2;
    } else {
      // Bila bukan fit dan cover, set ukuran target
      // sesuai dengan ukuran resize
      $TargetWidth = $ResizeWidth;
      $TargetHeight = $ResizeHeight;
    }

    // Buat Image Target
    $ImageTarget = imagecreatetruecolor($TargetWidth, $TargetHeight);

    if ($Type == self::Fit) {
      // Pastikan fill target terlebih dahulu untuk fit
      $Hitam = imagecolorallocate($ImageTarget, 0, 0, 0);
      imagefill($ImageTarget, 0, 0, $Hitam);
    }

    // Gambar dan resize image sumber ke image target
    // Gunakan imagecopyresampled agar menggunakan smooth scaling
    imagecopyresampled(
      $ImageTarget,
      $ImageSumber,
      $TargetX,
      $TargetY,
      0,
      0,
      $ResizeWidth,
      $ResizeHeight,
      $WidthSumber,
      $HeightSumber
    );

    // Return Image Target
    return $ImageTarget;
  }
}

Untuk menggunakan class tersebut, bisa dengan cara seperti berikut:

<?php
// Include class file
include_once "PropResize.php";

// Buat GDImage dari jpeg
$im = imagecreatefromjpeg("Hippopx.jpg");

// ScaleWidth:
$im2 = PropResize::resize($im, 320, 0, PropResize::ScaleWidth);

// ScaleHeight:
// $im2 = PropResize::resize($im, 0, 240, PropResize::ScaleHeight);

// Fit:
// $im2 = PropResize::resize($im, 320, 240, PropResize::Fit);

// Cover
// $im2 = PropResize::resize($im, 320, 240, PropResize::Cover);

// Set Header Content-type sebagai image/jpeg
header('content-type:image/jpeg');

// Output image hasil resize
imagejpeg($im2, null, 80);

Hasil dari test di atas sebagai berikut:

Sumber Gambar : hippopx 1920×1080
License to use Creative Commons Zero – CC0

Source code dapat didownload di Github Gists: https://gist.github.com/amarullz/2c5a1b0478fb22211d6f38c4139fe73b

Penutup

Diluar sana pasti banyak library yang siap pakai untuk digunakan, terutama bagi programmer yang menggunakan framework. Tapi mengetahui cara dan algoritma untuk melakukannya dapat menambah pengetahuan dan tentunya dapat mengetahui cara yang optimal ketika akan menggunakan library yang sudah siap pakai tersebut.

Pada kesempatan selanjutnya, Saya juga akan menjelaskan algoritma resize secara low-level dari level pixel, dan beberapa perbedaan kualitas resize dan performa dari algoritma resize yang digunakan.

Sampai jumpa lagi pada artikel selanjutnya.