Advertisement
  1. Code
  2. PHP

PHP를 이용한 이미지 크기 조정

Scroll to top
Read Time: 10 min

() translation by (you can also view the original English article)

PHP에서 이미지 크기를 조정하는 데 사용하기 쉬운 만능 방법이 필요했던 적이 있나요? 이것이 바로 PHP 클래스가 하는 일입니다. 즉, 내부적으로 지저분한 작업을 하기 위해 호출하는 재사용 가능한 기능 말이죠. 여기서는 구조적으로 짜임새 있으면서 확장 가능한 클래스를 직접 만드는 법을 배우겠습니다. 이미지의 크기를 조정하는 일이 쉬워질 것입니다. 얼마나 쉬울까요? 대략 세 단계면 어떨까요!


들어가며

이번 시간에 만들고자 하는 것을 잠깐 살펴보면 클래스는 다음과 같은 특성을 지녀야 합니다.

  • 사용하기 쉬움
  • 포맷에 독립적. 즉, 다양한 이미지 포맷을 열고, 크기를 조정하고, 저장함
  • 지능적인 크기 조정 - 이미지 왜곡이 없음!

참고: 이것은 클래스와 객체를 만드는 방법에 대한 튜토리얼이 아니며, 그 기술이 도움될지라도 이 튜토리얼을 실습하는 데 반드시 필요한 것은 아닙니다.

다뤄야 할 게 많습니다 - 그럼 시작하겠습니다.


1단계 준비

쉬운 것부터 시작하겠습니다. 작업 디렉터리에서 두 개의 파일을 만듭니다. 하나는 index.php라는 파일이고, 다른 하나는 resize-class.php입니다.


2단계 객체 호출

여기서 목표로 삼은 것에 대한 아이디어를 얻기 위해 이미지의 크기를 조정하는 데 사용할 호출 코드를 작성하는 것부터 시작하겠습니다. index.php 파일을 열고 다음 코드를 추가합니다.

보다시피 여기서 하려는 일에 대한 로직을 볼 수 있습니다. 이미지 파일을 열고 이미지의 크기와 크기 조정 유형을 설정합니다.
그런 다음 원하는 이미지 형식과 이미지 품질을 선택한 후 이미지를 저장합니다. index.php 파일을 저장하고 닫습니다.

1
  	// *** Include the class

2
		include("resize-class.php");
3
4
		// *** 1) Initialize / load image

5
		$resizeObj = new resize('sample.jpg');
6
7
		// *** 2) Resize image (options: exact, portrait, landscape, auto, crop)

8
		$resizeObj -> resizeImage(150, 100, 'crop');
9
10
		// *** 3) Save image

11
		$resizeObj -> saveImage('sample-resized.gif', 100);

위 코드에서는 jpg 파일을 열어 gif로 저장하는 것을 볼 수 있습니다. 유연성이 중요하다는 사실을 기억해 두세요.


3단계 클래스 골격

이 기능을 손쉽게 구현할 수 있게 하는 것은 바로 객체 지향 프로그래밍(OOP, Object-Oriented Programming)입니다. 클래스를 패턴이라고 생각해보십시오. 데이터를 캡슐화(데이터 은닉을 가리키는 또 다른 용어)할 수 있습니다. 그러고 나면 크기 조정 코드를 다시 작성할 필요 없이 이 클래스를 반복적으로 재사용할 수 있습니다. 즉, 2단계에서 했던 것과 마찬가지로 적절한 메서드만 호출하면 됩니다. 한번 패턴이 만들어지면 객체라고 하는 이 패턴의 인스턴스를 만듭니다.

"생성자로 알려진 생성 함수는 새 객체를 만들 때 클래스가 호출하는 특별한 클래스 메서드다."

resize 클래스를 만들어 봅시다. resize-class.php 파일을 엽니 다. 다음은 'resize'라는 이름의 아주 기본적인 클래스 골격 구조입니다. 클래스 변수를 주석으로 처리한 줄을 눈여겨봅시다. 이곳에 중요한 클래스 변수를 나중에 추가할 것입니다.

생성자로 알려진 생성 함수는 특별한 클래스 메서드입니다("메서드"라는 용어는 함수와 동일하지만 클래스와 객체에 대해 이야기할 때 메서드라는 용어를 자주 사용합니다)로서 이 함수는 새로운 객체를 생성할 때 클래스에 의해 호출됩니다. 생성자는 다음 단계에서 할 몇 가지 초기화 작업을 수행하는 데 적합합니다.

1
		Class resize
2
		{
3
			// *** Class variables

4
5
			public function __construct()
6
			{
7
8
			}
9
		}

생성 메서드에 두 개의 밑줄이 지정된 것을 눈여겨봅시다.


4단계 생성자

이번에는 앞에서 만든 생성자 메서드를 수정하려고 합니다. 먼저 크기를 조정할 이미지의 파일명(및 경로)을 전달하겠습니다. 여기서는 이 변수를 $fileName이라고 부르겠습니다.

PHP가 이미지를 읽을 수 있으려면 PHP(좀 더 구체적으로 말하자면 PHP GD 라이브러리)로 전달된 파일을 열어야 합니다. 여기서는 이 작업을 직접 작성한 'openImage' 메서드로 처리하려고 합니다. 이 메서드가 어떻게 동작하는지는
잠시후에 설명하겠지만 지금은 결과를 클래스 변수로 저장해야 합니다. 클래스 변수는 변수에 불과하지만 해당 클래스에만 국한된 것입니다. 앞에서 언급한 클래스 변수 주석을 기억하십니까? 그곳에 'private $ image;'를 입력해서 'image'를 비공개 변수로 추가합니다. 변수를 'private'으로 설정하면 해당 변수를 해당 클래스에서만 접근할 수 있게 유효범위를 설정하는 셈입니다. 이제 자원으로 알려진 열린 이미지를 호출할 수 있습니다. 리소스는 나중에 크기를 조정할 때 살펴보겠습니다.

그 사이에 이미지의 높이와 너비를 저장해 봅시다. 저는 이것들이 나중에 쓸모있을 것 같다는 느낌이 듭니다.

이제 코드가 다음과 같을 것입니다.

1
		Class resize
2
		{
3
			// *** Class variables

4
			private $image;
5
			private $width;
6
			private $height;
7
8
			function __construct($fileName)
9
			{
10
			    // *** Open up the file

11
			    $this->image = $this->openImage($fileName);
12
13
			    // *** Get width and height

14
			    $this->width  = imagesx($this->image);
15
			    $this->height = imagesy($this->image);
16
			}
17
		}

imagesx 및 imagesy 메서드는 GD 라이브러리에 포함된 함수입니다. 두 메서드는 이미지의 너비와 높이를 각각 조회합니다.


5단계 이미지 열기

이전 단계에서는 사용자 정의 메서드인 openImage를 호출했습니다. 이번 단계에서는 그 메서드를 만들겠습니다. 우리가 생각하는 바를 스크립트에서 처리하게 만들려면 어떤 파일 유형이 전달됐느냐에 따라 스크립트가 해당 이미지를 여는 데 어떤 GD 라이브러리 함수를 호출해야 할지 결정해야 합니다. 이는 switch 문으로 파일 확장자를 비교해서 손쉽게 처리할 수 있습니다.

이를 위해 크기를 조정할 파일을 전달하고 해당 파일 리소스를 반환하면 됩니다.

1
		private function openImage($file)
2
		{
3
		    // *** Get extension

4
		    $extension = strtolower(strrchr($file, '.'));
5
6
		    switch($extension)
7
		    {
8
		        case '.jpg':
9
		        case '.jpeg':
10
		            $img = @imagecreatefromjpeg($file);
11
		            break;
12
		        case '.gif':
13
		            $img = @imagecreatefromgif($file);
14
		            break;
15
		        case '.png':
16
		            $img = @imagecreatefrompng($file);
17
		            break;
18
		        default:
19
		            $img = false;
20
		            break;
21
		    }
22
		    return $img;
23
		}

6단계 이미지 크기를 조정하는 법

이 단계에서 중요한 작업이 이뤄집니다. 이 단계에서는 실제로 우리가 어떤 일을 할지 설명하기만 합니다. 여기서 뭔가 해야 할 일은 없습니다. 다음 단계에서는 크기 조정을 수행하기 위해 호출할 공개 메서드를 만들겠습니다. 따라서 이미지의 크기를 조정하는 방법에 대한 정보는 물론 너비와 높이를 전달하는 것이 좋습니다. 그럼 잠시 이 부분에 대해 이야기해 봅시다. 이미지를 어떤 정확한 크기로 조정해야 하는 상황이 있을 것입니다. 좋습니다. 이 같은 경우도 다뤄봅시다. 그런데 수백 개의 이미지의 크기를 조정해야 하고 각 이미지의 가로/세로 비율이 다른 경우(초상화 이미지를 생각해 보십시오)도 있을 것입니다. 정확한 크기로 조정하면 심각한 왜곡이 발생할 수 있습니다. 왜곡을 방지하는 방법을 살펴보면 다음과 같습니다.

  1. 가로/세로 비율을 유지하면서 새 이미지의 치수에 최대한 가깝게 이미지의 크기를 조정합니다.
  2. 새로운 이미지의 치수로 최대한 줄이고 나머지는 잘라냅니다.

두 가지 모두 필요에 따라 실행 가능한 방법입니다.

그럼, 위에서 설명한 것들을 모두 처리해 보겠습니다. 요약하면 다음과 같은 옵션을 제공하려고 합니다.

  1. 정확한 너비/높이로 크기를 조정합니다. (exact)
  2. 너비에 따라 크기를 조정합니다. 정확한 너비가 설정되고 가로/세로 비율에 따라 높이가 조정됩니다. (landscape)
  3. 높이에 따라 크기를 조정합니다. "너비에 따라 크기를 조정합니다"와 같지만 높이는 설정되고 너비가 동적으로 조정됩니다. (portrait)
  4. 옵션 2와 3을 자동으로 결정합니다. 다양한 크기의 사진이 있는 폴더를 순회할 경우 스크립트가 이를 어떻게 처리할지 결정하게 합니다. (auto)
  5. 크기 조정 후 잘라내기. 제가 가장 선호하는 옵션입니다. 정확한 크기에 왜곡도 없습니다. (crop)

7단계 크기 조정. 이미지 크기를 조정해 봅시다!

resize 메서드에는 두 부분이 있습니다. 첫 번째 부분은 앞에서 설명한 대로 사용자 정의 메서드를 만들어(그리고 물론 크기 조정 '옵션'을 전달합니다) 새 이미지의 최적의 너비와 높이를 구하는 것입니다. 너비와 높이는 배열로 반환되고 각각의 변수로 설정됩니다. 저는 별로 좋아하지 않지만 '참조로 전달'을 이용해도 됩니다.

두 번째 부분은 실제 크기 조정을 수행하는 부분입니다. 이 튜토리얼의 분량을 줄이기 위해 다음 GD 함수를 읽어보길 바랍니다.

또한 imagecreatetruecolor 메서드의 출력 결과(새로운 트루 컬러 이미지)를 클래스 변수로 저장합니다. 다른 클래스 변수와 함께 'private $imageResized;'를 추가합니다.

크기 조정은 GD 라이브러리라고 하는 PHP 모듈에서 수행됩니다. 여기서 사용하는 많은 메소드가 이 라이브러에서 제공됩니다.

1
		// *** Add to class variables

2
		private $imageResized;
1
		public function resizeImage($newWidth, $newHeight, $option="auto")
2
		{
3
4
			// *** Get optimal width and height - based on $option

5
			$optionArray = $this->getDimensions($newWidth, $newHeight, strtolower($option));
6
7
			$optimalWidth  = $optionArray['optimalWidth'];
8
			$optimalHeight = $optionArray['optimalHeight'];
9
10
			// *** Resample - create image canvas of x, y size

11
			$this->imageResized = imagecreatetruecolor($optimalWidth, $optimalHeight);
12
			imagecopyresampled($this->imageResized, $this->image, 0, 0, 0, 0, $optimalWidth, $optimalHeight, $this->width, $this->height);
13
14
			// *** if option is 'crop', then crop too

15
			if ($option == 'crop') {
16
				$this->crop($optimalWidth, $optimalHeight, $newWidth, $newHeight);
17
			}
18
		}

8단계 의사결정 트리

지금 하는 일이 많을수록 크기를 조정할 때 해야 할 일은 줄어 듭니다. 이번 메서드에서는 크기 조정 옵션에 따라 최적의 크기 조정 너비와 높이를 구하는 것을 목표로 어떤 처리 과정을 진행할지 선택합니다. 이 메서드에서는 다음 단계에서 만들 적절한 메서드를 호출할 것입니다.

1
		private function getDimensions($newWidth, $newHeight, $option)
2
		{
3
4
		   switch ($option)
5
		    {
6
		        case 'exact':
7
		            $optimalWidth = $newWidth;
8
		            $optimalHeight= $newHeight;
9
		            break;
10
		        case 'portrait':
11
		            $optimalWidth = $this->getSizeByFixedHeight($newHeight);
12
		            $optimalHeight= $newHeight;
13
		            break;
14
		        case 'landscape':
15
		            $optimalWidth = $newWidth;
16
		            $optimalHeight= $this->getSizeByFixedWidth($newWidth);
17
		            break;
18
		        case 'auto':
19
		            $optionArray = $this->getSizeByAuto($newWidth, $newHeight);
20
					$optimalWidth = $optionArray['optimalWidth'];
21
					$optimalHeight = $optionArray['optimalHeight'];
22
		            break;
23
				case 'crop':
24
		            $optionArray = $this->getOptimalCrop($newWidth, $newHeight);
25
					$optimalWidth = $optionArray['optimalWidth'];
26
					$optimalHeight = $optionArray['optimalHeight'];
27
		            break;
28
		    }
29
			return array('optimalWidth' => $optimalWidth, 'optimalHeight' => $optimalHeight);
30
		}

9단계 최적 치수

이 4개의 메서드가 어떤 일을 할지는 이미 앞에서 살펴봤습니다. 기초적인 수학 계산을 통해 가장 적합한 것을 계산할 뿐입니다.

1
		private function getSizeByFixedHeight($newHeight)
2
		{
3
		    $ratio = $this->width / $this->height;
4
		    $newWidth = $newHeight * $ratio;
5
		    return $newWidth;
6
		}
7
8
		private function getSizeByFixedWidth($newWidth)
9
		{
10
		    $ratio = $this->height / $this->width;
11
		    $newHeight = $newWidth * $ratio;
12
		    return $newHeight;
13
		}
14
15
		private function getSizeByAuto($newWidth, $newHeight)
16
		{
17
		    if ($this->height < $this->width)
18
		    // *** Image to be resized is wider (landscape)

19
		    {
20
		        $optimalWidth = $newWidth;
21
		        $optimalHeight= $this->getSizeByFixedWidth($newWidth);
22
		    }
23
		    elseif ($this->height > $this->width)
24
		    // *** Image to be resized is taller (portrait)

25
		    {
26
		        $optimalWidth = $this->getSizeByFixedHeight($newHeight);
27
		        $optimalHeight= $newHeight;
28
		    }
29
			else
30
		    // *** Image to be resizerd is a square

31
		    {
32
				if ($newHeight < $newWidth) {
33
					$optimalWidth = $newWidth;
34
					$optimalHeight= $this->getSizeByFixedWidth($newWidth);
35
				} else if ($newHeight > $newWidth) {
36
					$optimalWidth = $this->getSizeByFixedHeight($newHeight);
37
				    $optimalHeight= $newHeight;
38
				} else {
39
					// *** Sqaure being resized to a square

40
					$optimalWidth = $newWidth;
41
					$optimalHeight= $newHeight;
42
				}
43
		    }
44
45
			return array('optimalWidth' => $optimalWidth, 'optimalHeight' => $optimalHeight);
46
		}
47
48
		private function getOptimalCrop($newWidth, $newHeight)
49
		{
50
51
			$heightRatio = $this->height / $newHeight;
52
			$widthRatio  = $this->width /  $newWidth;
53
54
			if ($heightRatio < $widthRatio) {
55
				$optimalRatio = $heightRatio;
56
			} else {
57
				$optimalRatio = $widthRatio;
58
			}
59
60
			$optimalHeight = $this->height / $optimalRatio;
61
			$optimalWidth  = $this->width  / $optimalRatio;
62
63
			return array('optimalWidth' => $optimalWidth, 'optimalHeight' => $optimalHeight);
64
		}

10단계 잘라내기

자르기를 선택한 경우, 즉 'crop' 옵션을 사용한 경우 조금 더 밟아야 할 단계가 있습니다. 여기서는 이미지를 가운데에서부터
잘라낼 것입니다. 잘라내기는 크기 조정과 매우 비슷하지만 몇 가지 크기 조정 매개변수를 더 전달받습니다.

1
		private function crop($optimalWidth, $optimalHeight, $newWidth, $newHeight)
2
		{
3
			// *** Find center - this will be used for the crop

4
			$cropStartX = ( $optimalWidth / 2) - ( $newWidth /2 );
5
			$cropStartY = ( $optimalHeight/ 2) - ( $newHeight/2 );
6
7
			$crop = $this->imageResized;
8
			//imagedestroy($this->imageResized);

9
10
			// *** Now crop from center to exact requested size

11
			$this->imageResized = imagecreatetruecolor($newWidth , $newHeight);
12
			imagecopyresampled($this->imageResized, $crop , 0, 0, $cropStartX, $cropStartY, $newWidth, $newHeight , $newWidth, $newHeight);
13
		}

11단계 이미지 저장

이제 거의 끝났습니다. 이제 이미지를 저장할 시간입니다. 이미지를 저장할 경로를 비롯해 이미지 품질을 0-100(100이 가장 좋습니다)으로 전달한 후 적절한 메서드를 호출합니다. 이미지 품질에 대해 몇 가지 알아야 할 사항입니다. JPG는 0-100의 값을 사용하며, 100이 가장 높은 품질입니다. GIF 이미지에는 이미지 품질 설정이 없습니다. PNG는 이미지 품질 속성이 있으며, 0-9 범위를 사용하고, 0이 가장 높은 품질입니다. 이미지를 저장하고 싶을 때마다 이러한 옵션을 일일이 기억하지는 못할 것이므로 이렇게 처리하는 바람직하지 않습니다. 그래서 모든 것을 표준화하기 위해 약간의 마술을 부리겠습니다.

1
		public function saveImage($savePath, $imageQuality="100")
2
		{
3
			// *** Get extension

4
        	$extension = strrchr($savePath, '.');
5
        	$extension = strtolower($extension);
6
7
			switch($extension)
8
			{
9
				case '.jpg':
10
				case '.jpeg':
11
					if (imagetypes() & IMG_JPG) {
12
						imagejpeg($this->imageResized, $savePath, $imageQuality);
13
					}
14
		            break;
15
16
				case '.gif':
17
					if (imagetypes() & IMG_GIF) {
18
						imagegif($this->imageResized, $savePath);
19
					}
20
					break;
21
22
				case '.png':
23
					// *** Scale quality from 0-100 to 0-9

24
					$scaleQuality = round(($imageQuality/100) * 9);
25
26
					// *** Invert quality setting as 0 is best, not 9

27
					$invertScaleQuality = 9 - $scaleQuality;
28
29
					if (imagetypes() & IMG_PNG) {
30
						imagepng($this->imageResized, $savePath, $invertScaleQuality);
31
					}
32
					break;
33
34
				// ... etc

35
36
				default:
37
					// *** No extension - No save.

38
					break;
39
			}
40
41
			imagedestroy($this->imageResized);
42
		}

이제 이미지 리소스를 파기해서 여유 메모리를 확보할 좋은 기회입니다. 배포 환경에서 이 메서드를 사용한다면 저장된 이미지의 결과를 캡처하여 반환하는 것도 좋을 것입니다.


정리

이제 모두 끝났습니다. 이 튜토리얼을 따라와 주셔서 감사합니다. 이 튜토리얼이 도움됐으면 좋겠습니다. 아래 댓글로 여러분의 의견을 알려주시면 감사하겠습니다.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.