2025. 4. 17. 21:42ㆍ개발자 능력치 물약/파이썬
파이썬의 이미지 라이브러리인 Pillow를 활용해서 만들었습니다.
winapi 프로젝트를 진행하면서 리소스를 작업하는일이 많다. 직접 그림판 , Gimp로 작업하는것도 한계가 있다. 프로젝트 볼륨이 커지니까 필요한 리소스도 엄청많아졌다. 1000장정도 되는 파일들을 한번에 처리할 방법을 생각하다가 간단한 파이썬 코드로 자동화툴을 만들어서 쓰기로 했다.
1. png-> bmp 자동변환 툴
from PIL import Image
import os
def convert_png_to_bmp_recursive(source_dir, dest_dir=None, delete_original=False):
# 대상 디렉토리가 지정되지 않았으면 소스 디렉토리와 동일하게 설정
if dest_dir is None:
dest_dir = source_dir
# 변환된 파일 수 카운트
converted_count = 0
deleted_count = 0
print(f"시작: {source_dir}의 모든 PNG 파일을 BMP로 변환합니다...")
# 모든 파일과 하위 디렉토리 처리
for root, dirs, files in os.walk(source_dir):
# 상대 경로 계산
rel_path = os.path.relpath(root, source_dir)
# 현재 처리 중인 디렉토리에 대응하는 대상 디렉토리 생성
if rel_path != ".":
target_dir = os.path.join(dest_dir, rel_path)
else:
target_dir = dest_dir
if not os.path.exists(target_dir):
os.makedirs(target_dir)
print(f"폴더 생성: {target_dir}")
# 현재 디렉토리의 PNG 파일들 처리
for file in files:
if file.lower().endswith(".png"):
input_path = os.path.join(root, file)
output_path = os.path.join(target_dir, file.replace(".png", ".bmp").replace(".PNG", ".bmp"))
try:
# PNG를 BMP로 변환
img = Image.open(input_path)
img = img.convert("RGB") # 24비트 RGB로 변환
img.save(output_path, "BMP")
converted_count += 1
print(f"변환 완료: {input_path} -> {output_path}")
# 원본 파일 삭제 옵션이 켜져 있으면 삭제
if delete_original:
os.remove(input_path)
deleted_count += 1
print(f"원본 삭제: {input_path}")
except Exception as e:
print(f"변환 오류 {input_path}: {e}")
print(f"\n변환 완료! 총 {converted_count}개 파일 변환됨.")
if delete_original:
print(f"총 {deleted_count}개 원본 PNG 파일 삭제됨.")
if __name__ == "__main__":
# 사용자에게 최상위 디렉토리만 입력 받기
source_directory = input("PNG 파일이 있는 최상위 폴더 경로를 입력하세요: ")
# 사용자에게 입력받은 경로가 존재하는지 확인
if not os.path.exists(source_directory):
print(f"오류: {source_directory} 경로를 찾을 수 없습니다.")
exit()
# 출력 디렉토리 입력 (선택사항)
dest_directory = input("BMP 파일을 저장할 폴더 경로를 입력하세요 (그냥 Enter 누르면 원본과 같은 폴더에 저장): ")
if dest_directory.strip() == "":
dest_directory = source_directory
# 원본 삭제 여부 확인
delete_option = input("변환 후 원본 PNG 파일을 삭제할까요? (y/n): ").lower()
delete_original = delete_option == 'y' or delete_option == 'yes'
if delete_original:
confirm = input("주의: 원본 PNG 파일이 영구적으로 삭제됩니다. 계속하시겠습니까? (y/n): ").lower()
if confirm != 'y' and confirm != 'yes':
print("작업이 취소되었습니다.")
exit()
# 변환 실행
convert_png_to_bmp_recursive(source_directory, dest_directory, delete_original)
input("\n프로그램을 종료하려면 아무 키나 누르세요...")
파이썬 이미지 라이브러리인 Pillow 를 이용했다. 최상위 폴더경로를 입력하면 하위폴더까지 탐색할 수 있다. 또 매번 원본파일 삭제하는게 번거로워서 변환이 끝난뒤 원본파일을 삭제할 수 있게 만들었다.
2. 타일시트 자동생성툴
from PIL import Image
import os
import math
import glob
"""
사용법:
1. 이 스크립트를 실행합니다: python tilesheet_creator.py
2. 이미지가 있는 폴더 경로를 입력합니다
3. 하위 폴더 탐색 여부를 선택합니다 (y/n)
4. 출력할 타일시트 파일 경로를 입력합니다
5. 각 타일의 너비와 높이를 픽셀 단위로 입력합니다
6. 타일시트 레이아웃 설정 여부를 선택합니다 (y/n)
- 선택한 경우: 가로/세로 타일 수를 입력합니다 (자동 계산은 Enter)
"""
def create_tilesheet(source_dir, output_path, tile_width, tile_height, sheet_width=None, sheet_height=None, recursive=True):
source_dir = os.path.abspath(source_dir)
output_path = os.path.abspath(output_path)
print(f"이미지 검색 경로: {source_dir}")
image_files = []
if recursive:
for root, dirs, files in os.walk(source_dir):
for file in files:
if file.lower().endswith(('.png', '.bmp')):
image_files.append(os.path.join(root, file))
else:
png_files = glob.glob(os.path.join(source_dir, "*.png"))
bmp_files = glob.glob(os.path.join(source_dir, "*.bmp"))
png_files_upper = glob.glob(os.path.join(source_dir, "*.PNG"))
bmp_files_upper = glob.glob(os.path.join(source_dir, "*.BMP"))
image_files = png_files + bmp_files + png_files_upper + bmp_files_upper
if not image_files:
print(f"오류: {source_dir}와 그 하위 폴더에서 이미지 파일을 찾을 수 없습니다.")
print("지원되는 형식: .png, .PNG, .bmp, .BMP")
return False
print(f"총 {len(image_files)}개의 이미지 파일을 찾았습니다.")
image_files.sort()
num_images = len(image_files)
if sheet_width is None and sheet_height is None:
sheet_width = math.ceil(math.sqrt(num_images))
sheet_height = math.ceil(num_images / sheet_width)
elif sheet_width is None:
sheet_width = math.ceil(num_images / sheet_height)
elif sheet_height is None:
sheet_height = math.ceil(num_images / sheet_width)
final_width = sheet_width * tile_width
final_height = sheet_height * tile_height
# 배경색을 마젠타(255, 0, 255)로 설정
tilesheet = Image.new('RGB', (final_width, final_height), (255, 0, 255))
print(f"타일시트 생성 중: {sheet_width}x{sheet_height} 타일 ({final_width}x{final_height} 픽셀)")
print(f"배경색: 마젠타 (255, 0, 255)")
processed_images = []
for index, img_path in enumerate(image_files):
if index >= sheet_width * sheet_height:
print(f"경고: 지정된 타일시트 크기를 초과했습니다. {len(image_files) - index}개 이미지가 포함되지 않았습니다.")
break
try:
img = Image.open(img_path)
img = img.convert('RGB')
img_file = os.path.basename(img_path)
if img.width != tile_width or img.height != tile_height:
print(f"리사이즈: {img_file} ({img.width}x{img.height} -> {tile_width}x{tile_height})")
img = img.resize((tile_width, tile_height), Image.LANCZOS)
row = index // sheet_width
col = index % sheet_width
x = col * tile_width
y = row * tile_height
tilesheet.paste(img, (x, y))
processed_images.append(img_path)
print(f"처리 완료: {img_file} -> 위치 ({col}, {row})")
except Exception as e:
print(f"이미지 처리 오류 {img_path}: {e}")
output_dir = os.path.dirname(output_path)
if output_dir and not os.path.exists(output_dir):
try:
os.makedirs(output_dir)
print(f"출력 폴더 생성: {output_dir}")
except Exception as e:
print(f"출력 폴더 생성 오류: {e}")
return False
try:
ext = os.path.splitext(output_path)[1].lower()
if ext == '.bmp':
tilesheet.save(output_path, 'BMP')
elif ext in ['.jpg', '.jpeg']:
tilesheet.save(output_path, 'JPEG', quality=95)
elif ext == '.png':
tilesheet.save(output_path, 'PNG')
else:
if not ext:
output_path += '.bmp'
tilesheet.save(output_path, 'BMP')
print(f"\n타일시트 생성 완료: {output_path}")
print(f"총 이미지: {len(image_files)}, 처리된 이미지: {len(processed_images)}")
return True
except Exception as e:
print(f"타일시트 저장 오류: {e}")
return False
if __name__ == "__main__":
try:
source_directory = input("이미지 파일이 있는 폴더 경로를 입력하세요: ").strip()
source_directory = source_directory.strip('"\'')
if not os.path.exists(source_directory):
print(f"오류: 지정한 경로가 존재하지 않습니다: {source_directory}")
input("\n프로그램을 종료하려면 아무 키나 누르세요...")
exit()
if not os.path.isdir(source_directory):
print(f"오류: 지정한 경로가 폴더가 아닙니다: {source_directory}")
input("\n프로그램을 종료하려면 아무 키나 누르세요...")
exit()
recursive_option = input("하위 폴더까지 모두 탐색하시겠습니까? (y/n): ").lower()
recursive = recursive_option in ['y', 'yes']
output_file = input("생성할 타일시트 파일 경로를 입력하세요 (예: C:\\output\\tilesheet.bmp): ").strip()
output_file = output_file.strip('"\'')
try:
tile_width = int(input("각 타일의 너비(픽셀)를 입력하세요: "))
tile_height = int(input("각 타일의 높이(픽셀)를 입력하세요: "))
except ValueError:
print("오류: 타일 크기는 숫자로 입력해야 합니다.")
input("\n프로그램을 종료하려면 아무 키나 누르세요...")
exit()
sheet_layout = input("타일시트 레이아웃을 지정하시겠습니까? (y/n): ").lower()
sheet_width = None
sheet_height = None
if sheet_layout in ['y', 'yes']:
width_input = input("타일시트의 가로 타일 수를 입력하세요 (자동 계산은 Enter): ")
if width_input.strip():
try:
sheet_width = int(width_input)
except ValueError:
print("오류: 가로 타일 수는 숫자로 입력해야 합니다. 자동 계산됩니다.")
height_input = input("타일시트의 세로 타일 수를 입력하세요 (자동 계산은 Enter): ")
if height_input.strip():
try:
sheet_height = int(height_input)
except ValueError:
print("오류: 세로 타일 수는 숫자로 입력해야 합니다. 자동 계산됩니다.")
create_tilesheet(source_directory, output_file, tile_width, tile_height, sheet_width, sheet_height, recursive)
except Exception as e:
print(f"예상치 못한 오류가 발생했습니다: {e}")
input("\n프로그램을 종료하려면 아무 키나 누르세요...")
리소스가 타일 시트로 있는게 아니다 보니 직접 작업해야했다. 직접 이미지를 하나하나 사이즈맞춰서 타일시트를 만들기에는 정신적으로 힘들어서 자동화툴을 만들었다 .
파일경로를 입력하면 그 경로의 폴더, 하위폴더까지 검색해서 타일시트를 만들수있게 했다.
3. 배경색을 마젠타색으로 바꿔주는 툴
코드에서 생성할때 타일시트의 배경색을 검정색으로 설정해놔서 위의 그림처럼 검은색 배경이 생성됬다.
하지만 winapi에서 렌더할때 배경색을 마젠타로 하는게 편해서 배경색을 수정하려고 한다.
마젠타가 편한이유는 현실에서는 거의 안쓰는 색이기때문이다.
렌더할때 이미지의 그림에 마젠타와 겹치는 색이 없을 확률이커서
transparent color를 마젠타로 설정하면 깔끔한 이미지가 렌더된다.
from PIL import Image
import os
def change_background_color(input_path, output_path, old_color=(0, 0, 0), new_color=(255, 0, 255), delete_original=False):
try:
# 입력 파일이 존재하는지 확인
if not os.path.isfile(input_path):
print(f"오류: 입력 파일이 존재하지 않거나 폴더입니다: {input_path}")
return False
# 출력 경로가 디렉토리인지 확인하고, 디렉토리라면 파일명 추가
if os.path.isdir(output_path):
base_name = os.path.basename(input_path)
output_path = os.path.join(output_path, f"magenta_{base_name}")
print(f"출력 경로가 폴더이므로 파일명을 자동 생성합니다: {output_path}")
# 출력 디렉토리가 있는지 확인하고 없으면 생성
output_dir = os.path.dirname(output_path)
if output_dir and not os.path.exists(output_dir):
os.makedirs(output_dir)
print(f"출력 폴더를 생성했습니다: {output_dir}")
# 이미지 파일 열기
print(f"이미지 파일 열기: {input_path}")
image = Image.open(input_path)
# RGB 모드로 변환
image = image.convert('RGB')
# 픽셀 데이터 가져오기
pixels = image.load()
width, height = image.size
# 각 픽셀을 확인하여 배경색 변경
print("배경색 변경 중...")
for y in range(height):
for x in range(width):
pixel = pixels[x, y]
# 기존 배경색과 일치하면 마젠타로 변경
if pixel[0] == old_color[0] and pixel[1] == old_color[1] and pixel[2] == old_color[2]:
pixels[x, y] = new_color
# 변경된 이미지 저장
print(f"변경된 이미지 저장 중: {output_path}")
# 파일 확장자 확인
_, ext = os.path.splitext(output_path)
if not ext:
output_path += ".bmp"
print(f"확장자가 없어 .bmp를 추가했습니다: {output_path}")
image.save(output_path)
print(f"배경색이 {old_color}에서 {new_color}로 변경되어 {output_path}에 저장되었습니다.")
# 원본 파일 삭제 (옵션이 켜져 있을 경우)
if delete_original and input_path != output_path:
try:
os.remove(input_path)
print(f"원본 파일을 삭제했습니다: {input_path}")
except Exception as e:
print(f"원본 파일 삭제 중 오류 발생: {e}")
return True
except Exception as e:
print(f"오류 발생: {e}")
print(f"입력 파일: {input_path}")
print(f"출력 파일: {output_path}")
return False
if __name__ == "__main__":
# 사용자 입력 받기
input_file = input("기존 타일시트 파일 경로를 입력하세요: ").strip().strip('"\'')
if not os.path.exists(input_file):
print(f"오류: 입력 파일 또는 폴더가 존재하지 않습니다: {input_file}")
input("\n프로그램을 종료하려면 아무 키나 누르세요...")
exit()
# 기존 배경색 입력 (기본값: 검은색)
try:
print("기존 배경색을 입력하세요 (기본값: 0,0,0 - 검은색):")
r = input("R (0-255): ").strip() or "0"
g = input("G (0-255): ").strip() or "0"
b = input("B (0-255): ").strip() or "0"
old_color = (int(r), int(g), int(b))
except ValueError:
print("오류: 색상 값은 0-255 사이의 숫자여야 합니다. 기본값(0,0,0)을 사용합니다.")
old_color = (0, 0, 0)
# 출력 파일 경로
output_file = input("저장할 파일 경로를 입력하세요 (기본값: 원본 파일명_magenta): ").strip().strip('"\'')
if not output_file:
base_name, ext = os.path.splitext(input_file)
output_file = f"{base_name}_magenta{ext}"
if not ext:
output_file += ".bmp"
# 원본 삭제 여부 확인
delete_option = input("변환 후 원본 파일을 삭제하시겠습니까? (y/n): ").lower()
delete_original = delete_option == 'y' or delete_option == 'yes'
if delete_original and input_file == output_file:
print("경고: 입력 파일과 출력 파일이 같으므로 원본이 삭제되지 않습니다.")
delete_original = False
if delete_original:
confirm = input("주의: 원본 파일이 영구적으로 삭제됩니다. 계속하시겠습니까? (y/n): ").lower()
delete_original = confirm == 'y' or confirm == 'yes'
if not delete_original:
print("원본 파일 삭제를 취소했습니다.")
# 배경색 변경
change_background_color(input_file, output_file, old_color, (255, 0, 255), delete_original)
input("\n프로그램을 종료하려면 아무 키나 누르세요...")
이미지의 가로,세로만큼 픽셀을 탐색해서 입력한 RGB값이 있다면 magenta로 바꿔준다.
하지만 이 코드에 큰 단점이 있다. 이미지가 원본보다 조금 깨진다. 단순한 알고리즘으로 구현해서 경계선처리가 깔끔하지않다. 또 입력한 RGB값이 포함된곳은 전부 magenta로 바꾸다보니 텍스쳐가 뭉개지거나 이미지의 형태가 깨진다.
'개발자 능력치 물약 > 파이썬' 카테고리의 다른 글
파이썬 문자열 파싱을 이용해서 이미 푼 백준문제 깃허브연동하기 (0) | 2025.04.17 |
---|