Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 11 additions & 94 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,94 +1,11 @@
# Рефакторинг

В последнее время очень популярен [пиксельарт](https://en.wikipedia.org/wiki/Pixel_art), а также разнообразные наборы для ручного крафта.

Один из проектов, [mozabrick](https://mozabrick.ru/products/model-l/), например, предлагает что-то типа мозайки из квадратиков 5 градаций серого, из которой можно собрать любую фотографию.

![Преобразование](https://github.com/bibilov/refactoring/blob/main/img-test.png)

С помощью приложения вы накладываете фильтр, получаете черно-белый пиксель-арт, который можно набрать уже мозаикой.

Иногда делают такие панно прямо на зданиях.

![Здание](https://github.com/bibilov/refactoring/blob/main/632.jpg)


Вот я прогнал через фильтр свое фото:

![Фото с фильтром](https://github.com/bibilov/refactoring/blob/main/m0oLR8Tx0zRG8s3SZQlQLnF8bhcnGu6AwzRA5aqi.png_4_1.png)

Вот так примерно выглядит процесс сборки панно:

![Сборка](https://github.com/bibilov/refactoring/blob/main/compile.png)

Естественно, появилось желание написать вручную такой фильтр. Он может понадобится для пиксель-арта, создания игр с анимацией а-ля ранний Mortal Kombat, японских кроссвордов или для вязки свитеров ближе к НГ. Для чтения-записи изображений используется библиотека `pillow`, для всех остальных манипуляций — `numpy`.


```python
from PIL import Image
import numpy as np
img = Image.open("img2.jpg")
arr = np.array(img)
a = len(arr)
a1 = len(arr[1])
i = 0
while i < a - 11:
j = 0
while j < a1 - 11:
s = 0
for n in range(i, i + 10):
for n1 in range(j, j + 10):
n1 = arr[n][n1][0]
n2 = arr[n][n1][1]
n3 = arr[n][n1][2]
M = n1 + n2 + n3
s += M
s = int(s // 100)
for n in range(i, i + 10):
for n1 in range(j, j + 10):
arr[n][n1][0] = int(s // 50) * 50
arr[n][n1][1] = int(s // 50) * 50
arr[n][n1][2] = int(s // 50) * 50
j = j + 10
i = i + 10
res = Image.fromarray(arr)
res.save('res.jpg')
```

Картинка прелставляет сосбой трехмерный массив, где два измерения &mdash; таблица с пикселями, а пиксель &mdash; что-то то типа массива `[12, 240, 123]`, содержащего компоненты RGB.

Я ввел размер элемента мозайки 10x10 пикселей. Среди 100 пикселей из большой ячейки я просто выясняю среднюю яркость и закрашиваю их все в один цвет средней яркости, приведенный к ступеньке с шагом 50.

В результате из такой картинки:

![Исходная каритинка](https://github.com/bibilov/refactoring/blob/main/img2.jpg)

Получается такая:

![Результат](https://github.com/bibilov/refactoring/blob/main/res.jpg)

Это не тот результат, на который я рассчитывал, и вам предстоит много поработать с моим кодом.

Представьте этапы как отдельные коммиты.

## Что делать?

### 1 этап
К коду настолько много вопросов, что я даже не знаю с чего начать...

* В коде содержатся как минимум четыре ошибки, которые заставляют фильтр работать не так, как нужно.
* Одна из них очень нетипична для программиста на Питоне и связана с переполнением беззнакового целого `numpy.uint8`.
* В одном месте я запутался с именами переменных.
* Неверно считаю компоненты серого цвета (забываю поделить на 3).
* Неверно работаю с граничными условиями, в результате чего справа и внизу остались необработанные полосы по 10 пикселей.

### 2 этап
* PEP8.
* Именование переменных.
* Возможность управлять размерами мозайки (сейчас &mdash; только 10x10).
* Возможность управлять градациями серого (сейчас &mdash; с шагом 50). Лучше сделать просто в виде задания количества шагов. Например: 4 градации, 6 градаций.
* Выделение функций.
### 3 этап
* По возможности убрать ручные циклы, заменив их матричными преобразованиями.
### 4 этап
* Возможно, переписать в консольную утилиту, которой на вход подаются имена исходного изображения и результата. Сейчас чтобы заставить код работать с другой картинкой, его надо исправлять.
Профилирование

Новый фильтр ![новый фильтр](filter.png)
Старый фильтр ![новый фильтр](old_filter.png)
Время выполнения нового фильтра меньше за счёт оптимизаций количества операций
.........
Результат профилирования filter_with_filename.py ![filter_with_filename](filter_with_filename.png)
.........
doctest ![doctest](doctest.png)
.........
![img](scale_1200.jpg)
Binary file added doctest.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added filter.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
59 changes: 33 additions & 26 deletions filter.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,35 @@
from PIL import Image
import numpy as np
img = Image.open("img2.jpg")
arr = np.array(img)
a = len(arr)
a1 = len(arr[1])
i = 0
while i < a - 11:
j = 0
while j < a1 - 11:
s = 0
for n in range(i, i + 10):
for n1 in range(j, j + 10):
n1 = arr[n][n1][0]
n2 = arr[n][n1][1]
n3 = arr[n][n1][2]
M = n1 + n2 + n3
s += M
s = int(s // 100)
for n in range(i, i + 10):
for n1 in range(j, j + 10):
arr[n][n1][0] = int(s // 50) * 50
arr[n][n1][1] = int(s // 50) * 50
arr[n][n1][2] = int(s // 50) * 50
j = j + 10
i = i + 10
res = Image.fromarray(arr)
res.save('res.jpg')
import sys

def get_pixel_image(image, pixel_size = 10, gradation = 5):
gradation_img = 255 // gradation
array_img = np.array(image).astype(int)
length_img = len(array_img)
height_img = len(array_img[0])
i = j = 0
while i < length_img:
while j < height_img:
sector = array_img[i: i + pixel_size, j: j + pixel_size]
colors_sum = np.sum(sector)
average_color = int(colors_sum // (pixel_size ** 2))
set_color(int(average_color // gradation_img) * gradation_img / 3, array_img, pixel_size, i, j)
j += pixel_size
i += pixel_size
return Image.fromarray(np.uint8(array_img))


def set_color(new_color, vector, pixel_size, i, j):
for sector1 in range(i, i + pixel_size):
for sector2 in range(j, j + pixel_size):
for n in range(3):
vector[sector1][sector2][n] = new_color


if __name__ == "__main__":
source_name = sys.argv[1]
output_name = sys.argv[2]

source_img = Image.open(source_name)
array_img = np.array(source_img).astype(int)
get_pixel_image(array_img, int(input()), int(input())).save(output_name)
Binary file added filter_with_filename.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added old_filter.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added scale_1200.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.