본문 바로가기

Language/Python

[Python] JPG 그림 파일 용량 줄이기

연습 삼아 만들고 있는 혼자 쓸 툴에서 jpeg 이미지의 크기를 줄여야 하는 상황이 발생해서 검색을 해보다가

파이썬 2.7.x 인지 3.x인지 얘기도 없고 코드들을 보면 맞지 않는 것들도 좀 있고 그래서 답답하던 찰나...

(stackoverflow.com 사이트도 암튼 유저들의 집합이라 잘못된 정보 많은 듯.. ㅡㅡㅋ)


날이 추워서 계속 검색하기 귀찮아져서 그냥 검색한 2개의 예제문을 보고 코드 해석하고 안 맞는 부분은

유추해서 정리해보니 잘 되었다.


01. from cStringIO import StringIO
02. from PIL import Image
03.
04. file = r'C:\Users\Slays_Desktop\Pictures\logon_img\original.jpg'
05.
06. img = Image.open(file)        # 이미지 파일을 열어서 핸들링 한다. (그냥 open아니고 Image 모듈로 open한다.)
07. buffer = StringIO()              # 리사이징 될 jpg code를 저장해둘 메모리 버퍼를 확보한다.
08. img.save(buffer, 'jpeg', quality=85)        # 이미지를 jpeg 형식으로 85 수준의 퀄리티로 하여 버퍼에 저장한다.
09. buffer.seek(0)                                   # 버퍼의 스트림를 맨 앞으로 땡겨온다.(안 해도 되지만 혹시나...)
10. with open('resize.jpg', 'wb') as nfile:
11. nfile.write(buffer.getvalue())                        # 버퍼 값을 가져와 바이너리 형식으로 파일에 저장시킨다.

(buffer.getvalue()가 버퍼 값을 가져와주는 것)


정리를 따로 자세히 해보려다 그냥 주석으로 코드 옆에 남겨둔다.

검색한 것들에선 11번 줄은 원래 검색해본 것에선 getvalue()가 아니라 무슨 contents() 메소드를 쓰라고 되어 있는데,

내게 그런 메소드 따위 없었다. StringIO 라이브러리나 cStringIO 라이브리러나 다 그런 거 내겐 없었음 ㅠㅠ

getvalue()를 쓰니깐 버퍼에 저장된 내용물을 써먹을 수가 있었다.


그리고 8번째 줄의 quailty 부분 얘긴데, 저것에 넣는 수치가 % 인 것 같은데, 100 수치를 줘도 원본과 이미지의 크기가 똑같지는 않다. 뭐 거~~의 용량의 차이가 크진 않는데, 이미지 프로세싱의 구조적 문제 때문에 미세하게 다른 것이겠지!

암튼 수치를 줄여주면 JPEG 포맷의 파일은 이미지 용량이 줄어든다.


또 웃긴 건 바이너리 파일을 다루는 것인데 저장될 파일을 만들 때 wb 로 오픈해야 하는 걸

w로만 하라고 검색되더라. w로 오픈한 파일에 코드들을 저장시키면 jpg 이미지 다 깨진다.


머 암튼 저장한 이미지의 사이즈는 저런 차이를 보였다. (quailty 85% 짜리를 줄 때의 용량 차이다.)




[그림. 1] 원본과 리사이징 사진


- original.jpg : 1,056,382 byte

- resize.jpg : 224,238 byte




참고로 라이브러리에 대한 얘기를 잠깐 하자면


cStringIO은 StringIO의 형제? 급인 모듈이며 그 넘과 비슷한 기능을 제공해준다고 나오는데

cStringIO가 그냥 StringIO 보다 속도적인 면에서 더 낫다고 카더라(통신이 있었음) 그래서 난 cStringIO 씀.


StringIO에 대한 구글신의 번역은 다음과 같다. (cString도 기능적인 면은 이와 대동소이하다고 했었음.)


"이 모듈은 문자열 버퍼 (메모리 파일이라고도 함)를 읽고 쓰는 파일과 비슷한 클래스 인 StringIO를 구현합니다."

(메모리를 이용하니 당연히 속도가 더 빠르겠지)


아무튼 저렇게 하면 JPEG 파일의 이미지를 불러와 용량을 줄인 후에 새롭게 저장을 시킬 수가 있게 된다.


만약 저장할 이미지의 사이즈 정보를 미리 알려면??


버퍼에 저장된 값을 가져오면서 동시에 __len__() 메소드를 이용해주면 되겠다.


buffer.getvalue().__len__()


이렇게 해주면 int 형으로 용량의 값을 알려준다. 출력을 하건 어딘가 변수로 넣어서 활용하건 그건 유저의 몫임.