Python/Python Challenge

[Python Challenge 27] 지퍼올리세요

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] 빙글빙글

 

[Python Challenge 14] 빙글빙글

Python Challenge 14의 Url은 다음과 같다 Python challenge 14 : http://www.pythonchallenge.com/pc/return/italy.html 구성 페이지에는 사진이 2개 있다. 위에 보이는 나선빵은 italy라는 이름을 가지고 있고,..

tutoreducto.tistory.com

일단 zigzag.gif로 들어가면 14번문제와 유사한 사진을 구할 수 있다. 

<14번 문제>                  <27번문제>

* 실제 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은 아름다운 그림은 아니다.

<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도 유의미하진 못하다. 잘 생각해보자

<answer_flase.gif>

 

끝까지 힌트는 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")

길었다 참

<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