Python Challenge - Level 16



Clue 1

Page Title:

let me get this straight

Clue 2

Image: There are pink segments, looks like they have the same length.


Sounds like we need to align the pink segments. But how do you define "pink"

Find the Pink Segments

First load the image:

from PIL import Image, ImageChops

image = Image.open("mozart.gif")

Could it be the most frequent pixels? image.histogram() can give us the counts of each pixel.

>>> max(enumerate(image.histogram()), key=lambda x: x[1])
(60, 29448)

So value 60 appears 29448 times, let's see if it is pink. We can make a copy of the current image(to use the same palette), and paint all the pixels as 60:

>>> tmp = image.copy()
>>> tmp.frombytes(bytes([60] * (tmp.height * tmp.width)))
>>> tmp.show()

Hmm, not so pinky.

If pink segments exists in every row, then the number of pink pixels should be divisible by height of the image, right?

>>> [x for x in image.histogram() if x % image.height == 0 and x != 0]

Good, there's only one!

>>> image.histogram().index(2400)

Let's try pixel value of 195 instead of 60

>>> tmp.frombytes(bytes([195] * (tmp.height * tmp.width)))
>>> tmp.show()

Now it looks right.

Shift The Rows

Let's shift each row:

import numpy as np
>>> shifted = [bytes(np.roll(row, -row.tolist().index(195)).tolist()) for row in np.array(image)]

We are using np.array(image) to load image data, alternatively you can use list(image.getdata()), however you will get a one-dimension array, so you need to reshape the array manually.

We take each row in the ndarray, and get the first position of the pink pixel by row.tolist().index(195), then np.roll() can help us shift the array.

Once we have the shifted data as bytes, we can use Image.frombytes() to create a new image, while reusing the original image's mode and size.

>>> Image.frombytes(image.mode, image.size, b"".join(shifted)).show()

Here's the result:


Solution 1

from PIL import Image, ImageChops
import numpy as np
image = Image.open("mozart.gif")
shifted = [bytes(np.roll(row, -row.tolist().index(195)).tolist()) for row in np.array(image)]
Image.frombytes(image.mode, image.size, b"".join(shifted)).show()

Solution 2

from PIL import Image, ImageChops

image = Image.open("mozart.gif")

for y in range(image.size[1]):
    box = 0, y, image.size[0], y + 1
    row = image.crop(box)
    bytes = row.tobytes()
    i = bytes.index(195)
    row = ImageChops.offset(row, -i)
    image.paste(row, box)


Result: romance!

Next Level