Python Challenge 27의 Url은 다음과 같다
Python challenge 27 : http://www.pythonchallenge.com/pc/hex/speedboat.html
구성
speedboat.html이라는 페이지 위치에 맞게 보트로 노를 젓고 있는 사진을 한장 확인할 수 있다. 그림의 img태그에는 src로 링크가 걸려있다. 이동하면 자격증명을 물어보는 화면이 나오나, 우리는 아직 모르니 넘어가자
다른 힌트는 보이지 않으니 주석을 확인해 보자
유의미한 주석은 2개가 보인다.
<!--gif를 말씀하신건가요?>
<!-- 오, 14번문제의 반복은 아닙니다!(도얏)>
이렇게인데, 14번문제는 italy.html로 나선형으로 픽셀을 재배치 하는 문제였다. 아래참고
2021.05.21 - [Python/Python Challenge] - [Python Challenge 14] 빙글빙글
일단 zigzag.gif로 들어가면 14번문제와 유사한 사진을 구할 수 있다.
* 실제 14번 문제의 사진은 일렬로 정렬되어있었다.
27번문제도 픽셀의 재배치 인걸로 보이나, 방법을 조금 다르게 해야할 거 같다.
해결 아이디어
zigzag가 문제를 풀 수 있는 크나큰 힌트이다. 문제 많이 어렵고, 직관적인 판단을 요구한다. 이미지 픽셀을 재배치하는 것은 분명 어렵지 않은 일이 여야하는데, 지그재그로 재배치해서 사각형을 어떻게 만들 수 있을까? 우리 기본적인 걸로 돌아가보자
"이미지는 무엇을 이루어져 있는가?"
맞다. 픽셀이다.(벡터형은 나가있어) 그러면
"픽셀은 무엇으로 이루어져 있는가?"
맞다. 모드에 따라 다르지만, RGB의 3bytes의 조합으로 이루어져 있다. 우리의 zigzag.gif를 확대해서 보면, 흰색(0,0,0)~검은색(255,255,255)으로 이루어져 있다는 것을 알 수 있다. 이걸 어떻게 지그재그화 할까?
팔래트
팔레트(palette)는 컴퓨터 그래픽스에서 디지털 이미지 관리를 위해 존재하는 색
의 유한 집합이다. 컴퓨팅 이외에 쓰는 색상표는 컬러차트라고 한다.
- 출처 위키백과 : https://ko.wikipedia.org/wiki/%ED%8C%94%EB%A0%88%ED%8A%B8_(%EC%BB%B4%ED%93%A8%ED%8C%85) -
이번에 문제에 사용할 중요한 Keyword이다. 이미지중 "P"모드로 작성된 이미지는 모두 이 색상 유한집합(팔레트)를 가진다. 팔래트는 이미지에 사용된 색들을 포함한다. 당연하게도
>>> img = Image.open("zigzag.gif")
>>> img.mode
'P'
우리의 이미지는 'P'모드로 작성되어 있다. 팔래트를 따보자
>>> pal = img.getpalette()
>>> pal
[37, 37, 37, 229, 229, 229, 162, 162, 162, 136, 136, 136, 59,
59, 59, 212, 212, 212, 9, 9, 9, 41, 41, 41, 24, 24, 24, 156,
156, 156, 148, 148, 148, 112, 112, 112, 254, 254, 254, 91, 91,
91, 106, 106, 106, 49, 49, 49, 248, 248, 248, 213, 213, 213,
220, 220, 220, 15, 15, 15, 85, 85, 85, 159, 159, 159, 62, 62,
62, 78, 78, 78, 76, 76, 76, 111, 111, 111, 103, 103, 103, 150,
150, 150, 154, 154, 154, 68, 68, 68, 25, 25, 25, 169, 169, 169,
126, 126, 126, 185, 185, 185, 140, 140, 140, 234, 234, 234, 244,
...
역시 보이던 대로 흰색(0,0,0) ~ 검정색(255,255,255) 사이의 색들이라 같은 바이트가 3번 반복된다. 팔래트의 최대 길이는 256으로 지정되어 있으니, 3개씩 끊어 읽으면 256의 길이가 나올 것이다.
>>> pal = img.getpalette()[::3]
>>> len(pal)
256
정확했다. 신기한건 이렇게 구한 pal에 중복되는 수가 없다는 것이다. 256이라는 숫자는 또한 8비트로 나타낼 수 있는 최대 수의 개수이다.(0~255) 따라서 0~255에 매칭되게 pal에 변환 테이블을 작성해 주고 그 바이트를 다시 이미지로 바꾸면 유의미한 결과를 기대할 수 있을 것이다.
### 27_1.py
from PIL import Image
if __name__=="__main__" :
img = Image.open("zigzag.gif")
pal = img.getpalette()[::3]
trans = bytes.maketrans(bytes(list(range(256))),bytes(pal))
raw = img.tobytes()
trans = raw.translate(trans)
img2 = Image.new("RGB",img.size)
col = list()
for i in trans :
col.append((i,i,i))
img2.putdata(col)
img2.save("false.gif")
그러나 아쉽게도 얻은 false.gif은 아름다운 그림은 아니다.
생각해보자. 이번 문제의 TItle은 zigzag이다. 두개를 교차해야되는 거라면??? 어쩌면 두 그림을 교차할때 같은 픽셀값을 가지면 검정색으로 색칠하면 유의미한 그림이 나오지 않을까?
from PIL import Image
### 27_2.py
if __name__=="__main__" :
img = Image.open("zigzag.gif")
pal = img.getpalette()[::3]
trans = bytes.maketrans(bytes(list(range(256))),bytes(pal))
raw = img.tobytes()
trans = raw.translate(trans)
col = list(zip(raw,trans))
conv = [i for i,val in enumerate(col) if val[0]!=val[1]]
img3 = Image.new("RGB",img.size)
colors = [(255,255,255)]*len(raw)
for i in conv :
colors[i] = (0,0,0)
img3.putdata(colors)
img3.save("answer_false.gif")
그러나.... 결과로 나온 answer_false도 유의미하진 못하다. 잘 생각해보자
끝까지 힌트는 zigzag이다. speedboat사진에서 본 zigzag모양을 기억하는가
그렇다. 둘은 같은 선상에 있지 않다. 먼저나온 raw는 인덱스 1부터 가야되고, 나중에 나온 trans는 마지막 인덱스를 포함하면 안된다.
from PIL import Image
### 27_3.py
if __name__=="__main__" :
img = Image.open("zigzag.gif")
pal = img.getpalette()[::3]
trans = bytes.maketrans(bytes(list(range(256))),bytes(pal))
raw = img.tobytes()
trans = raw.translate(trans)
col = list(zip(raw[1:],trans[:-1]))
conv = [i for i,val in enumerate(col) if val[0]!=val[1]]
img3 = Image.new("RGB",img.size)
colors = [(255,255,255)]*len(raw)
for i in conv :
colors[i] = (0,0,0)
img3.putdata(colors)
img3.save("real_answer.gif")
길었다 참
아쉽게도 안끝났다. 사진을 보면 keyword가 아니라고 한다. 아래에는 busy?라는 자주보던 문자열이 보인다.
우리가 사용한 이 bytes열. 다시 decompress하면 어떨까?
### 27_4.py
from PIL import Image
import bz2
if __name__=="__main__" :
img = Image.open("zigzag.gif")
pal = img.getpalette()[::3]
trans = bytes.maketrans(bytes(list(range(256))),bytes(pal))
raw = img.tobytes()
trans = raw.translate(trans)
col = list(zip(raw[1:],trans[:-1]))
conv = [val[0] for i,val in enumerate(col) if val[0]!=val[1]]
cand = bz2.decompress(bytes(conv))
>>> print(cand[:140])
b'../ring/bell.html del assert repeat raise or class is exec return
except print return switch from exec repeat else not while assert or class'
>>> len(cand)
70644
결과로 나온 cand는 엄청 많은 조합의 keyword로 되어있다. 사진에는 keyword가 아니라고 하니까 모듈 keyword의 힘을 빌려 필터링 해보자
>>> nk = set([s for s in cand.split() if not keyword.iskeyword(s.decode())])
>>> print(nk)
{b'print', b'switch', b'exec', b'../ring/bell.html', b'repeat'}
중복이 엄청많아 set자료형으로 사용했다. 문제가 만들어 질때는 모르겠는데, exec와 print는 현재 키워드가 아니다.
남은건 switch와 repeat이다. 찾았다.
Answer Url : http://www.pythonchallenge.com/pc/ring/bell.html
Username : repeat
Password : switch
'Python > Python Challenge' 카테고리의 다른 글
[Python Challenge 29] Shut up Mr.Drake (6) | 2021.07.06 |
---|---|
[Python Challenge 28] 륑륑륑 (2) | 2021.06.29 |
[Python Challenge 26] 과거의 행실을 돌아보세요 (0) | 2021.06.06 |
[Python Challenge 25] 퍼즐맞추기 (0) | 2021.06.06 |
[Python Challenge 24] 보너스 타임! 기본으로 돌아갈 때 (0) | 2021.06.05 |