초간단 아날로그 시계 만들기 - GPT를 이용한 코딩 연습 - 9부(배포판 수정, 최적화 2)
2024. 10. 28. 15:55ㆍ코딩 연습/아날로그 시계 만들기
기존 코드는 창을 끌어 이동하거나, 창 크기를 변경하면 이벤트 호출로 배경 이미지를 다시 불러와 현재 창 크기에 맞게 조정했다. 생각보다 이게 버벅임을 유발한다. 지난 시간에 이어 최적화를 진행한다.
창을 이동하거나 크기를 변경할 때 발생한 엄청난 렉은 지난 시간에 고치긴 했다. 그래도 배경이미지를 다시 처리하는 데 있어 약간의 렉이 있었다. 이를 GPT에 고쳐달라고 했다. 처음엔 말귀를 못알아 듣고 엉뚱하게 코드를 고쳐 두어번의 시도 끝에 코드를 하나 받았다. 우선 기존 코드를 보자.
def load_background_image(self, image_path):
# 배경 이미지를 불러와서 캔버스에 표시하는 함수
self.canvas.delete("background") # 기존 배경 삭제
try:
image = Image.open(image_path)
# 캔버스 크기 얻기
canvas_width = self.canvas.winfo_width()
canvas_height = self.canvas.winfo_height()
# 이미지와 캔버스 비율 계산
image_ratio = image.width / image.height
canvas_ratio = canvas_width / canvas_height
# 이미지의 비율에 맞춰 크롭 영역 설정
if image_ratio > canvas_ratio:
# 이미지가 더 넓을 때, 가로 부분을 잘라냄
new_width = int(image.height * canvas_ratio)
left = (image.width - new_width) // 2
image = image.crop((left, 0, left + new_width, image.height))
else:
# 이미지가 더 길 때, 세로 부분을 잘라냄
new_height = int(image.width / canvas_ratio)
top = (image.height - new_height) // 2
image = image.crop((0, top, image.width, top + new_height))
# 캔버스 크기에 맞춰 이미지 축소 (비율 유지)
image.thumbnail((canvas_width, canvas_height), Image.LANCZOS)
# 크롭된 이미지를 캔버스에 표시
self.background_image = ImageTk.PhotoImage(image)
self.canvas.create_image(canvas_width / 2, canvas_height / 2,
anchor="center", image=self.background_image,
tags="background")
except Exception as e:
print(f"Error loading background image: {e}")
그리고 GPT가 수정해 준 아래 코드를 보자
def load_background_image(self, image_path):
# 배경 이미지를 불러와서 캔버스에 표시하는 함수
if not hasattr(self, 'original_image'):
# 원본 이미지를 메모리에 저장하여 반복 호출 시 재사용
self.original_image = Image.open(image_path)
# 캔버스 크기 얻기
canvas_width = self.canvas.winfo_width()
canvas_height = self.canvas.winfo_height()
# 이전에 캐시한 크기와 같다면 재조정하지 않음
if hasattr(self, 'last_canvas_size') and self.last_canvas_size == (canvas_width, canvas_height):
# 현재 크기가 이전 크기와 같다면 재조정하지 않고 그대로 사용
return
self.last_canvas_size = (canvas_width, canvas_height) # 현재 크기 저장
# 이미지를 조정하여 캔버스에 맞추기
image = self.original_image.copy() # 원본 이미지에서 복사본 생성
# 이미지와 캔버스 비율 계산
image_ratio = image.width / image.height
canvas_ratio = canvas_width / canvas_height
# 이미지의 비율에 맞춰 크롭 영역 설정
if image_ratio > canvas_ratio:
# 이미지가 더 넓을 때, 가로 부분을 잘라냄
new_width = int(image.height * canvas_ratio)
left = (image.width - new_width) // 2
image = image.crop((left, 0, left + new_width, image.height))
else:
# 이미지가 더 길 때, 세로 부분을 잘라냄
new_height = int(image.width / canvas_ratio)
top = (image.height - new_height) // 2
image = image.crop((0, top, image.width, top + new_height))
# 캔버스 크기에 맞춰 이미지 축소 (비율 유지)
image.thumbnail((canvas_width, canvas_height), Image.LANCZOS)
# 크롭된 이미지를 캔버스에 표시
self.background_image = ImageTk.PhotoImage(image)
self.canvas.create_image(canvas_width / 2, canvas_height / 2,
anchor="center", image=self.background_image,
tags="background")
if hasattr(self, 'last_canvas_size') and self.last_canvas_size == (canvas_width, canvas_height):
return
self.last_canvas_size = (canvas_width, canvas_height)
이 부분에서 last_canvas_size 변수를 이용해 이전 창 크기를 저장하고 이것과 비교해 창 크기가 변하지 않으면 다시 사진 크기 조정 연산을 하지 않는다. 창을 끌어 이동할 때 렉은 이걸로 해결된 것이다.
self.original_image = Image.open(image_path) 이걸로 이미지를 인스턴스 변수에 넣어준다. 이걸로 디스크에서 이미지를 한번 읽고 계속 메모리에 상주시켜 디스크에서 반복적으로 읽는 것을 막는다.
image = self.original_image.copy(), origanal_image를 바로 변경하면 다음번에 창크기를 조절할 때 변경된 이미지를 사용하괴 된다. 그래서 메모리에서 복사본을 만들어 이걸로 이미지를 변경한다.
정리하자면, 창 크기가 변경되지 않을 경우(창만 이동할 경우) 불필요한 연산을 막는다. 이미지를 메모리에 상주시켜 디스크에서 반복적으로 불러오는 것을 막는다. 두 가지 최적화를 한 것이다.
그런데 몇가지 문제가 있다.
기존에 있던 self.canvas.delete("background") # 기존 배경 삭제
코드를 어쩐일인지 지웠다. 이게 없으면 기존 canvas가 쌓여 성능저하를 야기할 수 있다.
더 치명적인 버그는 아래 부분인데
if not hasattr(self, 'original_image'):
# 원본 이미지를 메모리에 저장하여 반복 호출 시 재사용
self.original_image = Image.open(image_path)
이렇게 되면 이미지를 다른 사진으로 변경하더라도 기존 이미지가 있기 때문에 변경되지 않는다. 물론 레지스트리 주소값은 변경되기 때문에 프로그램을 껐다켜면 반영은 되어 있다. 하지만 프로그램 실행 중에는 메모리에 상주시킨 이미지가 바뀌지 않기 때문에 이미지는 변경되지 않는다.
GPT에게 수정을 해달라고 했더니 if문을 없앴다(?!). 최적화를 한 효과가 없어진다. 다시 최적화를 해달라고 했다.
if not hasattr(self, 'current_image_path') or self.current_image_path != image_path:
# 새로운 이미지를 로드하고 경로를 저장
self.original_image = Image.open(image_path)
self.current_image_path = image_path # 현재 이미지 경로를 저장
이제 이미지가 불러와졌는지만 확인하는 게 아니라 이전 경로와 현재 경로가 일치하는지 확인하여 이미지를 불러온다.
근데 여기도 맹점이 있다. 사진을 바꿔도 창 크기를 바꾸지 않는 이상 바뀌지 않는다. GPT에게 물어봐도 이젠 헛소리만 한다. 편하게 코딩하다가 머리를 굴려야 할 때가 왔다.
if not hasattr(self, 'current_image_path') or self.current_image_path != image_path:
# 새로운 이미지를 로드하고 경로를 저장
self.original_image = Image.open(image_path)
self.current_image_path = image_path # 현재 이미지 경로를 저장
self.changed_image = True
# 이전에 캐시한 크기와 같다면 재조정하지 않음
if hasattr(self, 'last_canvas_size') and self.last_canvas_size == (canvas_width, canvas_height) and not self.changed_image:
# 현재 크기가 이전 크기와 같다면 재조정하지 않고 그대로 사용
return
# ...... 생략 ......
self.changed_image = False
changed_image 란 변수를 만들고 새로운 경로일 경우 True 상태로 저장한다. 그리고 이 변수가 True 라면 이미지를 재조정하지 않고 그대로 끝내는 if문을 통과하게 만들었다. 그리고 마지막에 다시 변수를 False로 바꿔준다.
실행해보니 잘 된다.
이제 이미지(사진)이 화면 크기보다 작을 경우 화면에 꽉 차지 않는 걸 해결해보자. GPT에게 물어보니 간단하다.
image.thumbnail((canvas_width, canvas_height), Image.LANCZOS) # 대신
image = image.resize((canvas_width, canvas_height), Image.LANCZOS) # 를 사용합니다.
thumbnail 대신 resize를 사용하면 된다.
중요한 것은 thumbnail 메서드는 원본 이미지를 직접 변경 하므로 호출만 하면 변경이 된다. 하지만 resize 메서드는 새로운 이미지 객체를 반환하기 때문에 결과를 변수에 다시 저장해줘야한다. 그래서 "image = " 이 앞에 들어간다.
확인을 해보니 저해상도 사진도 화면이 꽉 차게 들어간다. 이상없이 작동하니 참고용으로 전체 코드를 첨부한다.
아이콘 파일
파이썬 코드
그런데 코드를 둘러보니 시계 테두리와 시간 구분 선 역시 매초 업데이트를 하고 있었다. 이 부분도 해상도 변경이 없다면 업데이트가 필요 없는 부분이다. 다음 글에서 수정해보도록 하겠다.
'코딩 연습 > 아날로그 시계 만들기' 카테고리의 다른 글
초간단 아날로그 시계 만들기 - GPT를 이용한 코딩 연습 - 11부(기능 추가, 프로그램 정보) (4) | 2024.11.04 |
---|---|
초간단 아날로그 시계 만들기 - GPT를 이용한 코딩 연습 - 10부(배포판 수정, 최적화 3) (0) | 2024.10.28 |
초간단 아날로그 시계 만들기 - GPT를 이용한 코딩 연습 - 8부(배포판 수정, 최적화 1) (3) | 2024.10.27 |
초간단 아날로그 시계 만들기 - GPT를 이용한 코딩 연습 - 7부(배포판 수정, 레지스트리를 사용해 저장하기) (1) | 2024.10.24 |
초간단 아날로그 시계 만들기 - GPT를 이용한 코딩 연습 - 6부(배포판 만들기) (3) | 2024.10.24 |
초간단 아날로그 시계 만들기 - GPT를 이용한 코딩 연습 - 5부(1차 완성) (1) | 2024.10.23 |
초간단 아날로그 시계 만들기 - GPT를 이용한 코딩 연습 - 4부 (2) | 2024.10.22 |
초간단 아날로그 시계 만들기 - GPT를 이용한 코딩 연습 - 3부 (0) | 2024.10.22 |