Python create png image

Introduction πŸ”—

PNG is the most common lossless image format on the web. As I am fascinated by the most mundane building blocks of the web, I thought it would be interesting to go through a demonstration of how to generate a PNG image file from scratch in Python.

I will be covering only the very basics on how to produce some output, and as the format is simple, there is no need to use anything but the standard Python library.

Image Representation πŸ”—

Let’s figure out what we want to visualise in our PNG image. As this is not a painting class, we will only generate a simple checkerboard pattern and spend the rest of our time focusing on writing that to a PNG file.

First of all, we need to decide how we represent a pixel. A common way to do this is to store the pixel as a sequence of 3 values, one for each of the RGB (red, green, blue) components. Let’s define that as a Python type alias:

from typing import Tuple Pixel = Tuple[int, int, int]

What pixels do we want to store? As we will be generating a checkerboard pattern, we only need black and white pixels. Let’s go ahead and define them as constants:

BLACK_PIXEL: Pixel = (0, 0, 0) WHITE_PIXEL: Pixel = (255, 255, 255)

We are representing the intensity of each colour as a value in the range of 0-255, which thankfully fits in one byte (3 bytes per pixel).

Now that we have pixels, how do we represent the image? For simplicity’s sake, we will use a list of pixel lists, where each pixel list is a row in the image, and the number of pixel lists is the height of the image. We will make an assumption that all pixel lists have the same length (i.e. every row has the same width). Let’s define the image as another type alias:

from typing import List Image = List[List[Pixel]]

Generating the Image πŸ”—

Now let’s write a simple function to generate our checkerboard pattern and return it as our image representation:

def generate_checkerboard_pattern(width: int, height: int) -> Image: out = [] for i in range(height): # Generate a single row of white/black pixels row = [] for j in range(width): if (i + j) % 2 == 0: row.append(WHITE_PIXEL) else: row.append(BLACK_PIXEL) out.append(row) return out

Note: As the checkerboard pattern consists of just two colours, we don’t need all of the RGB values. We could’ve stored this as a greyscale image and saved some space, or as an indexed-colour image with a palette and saved even more space. However, as most images on the web have colours, we will keep it as a colour image and stick to the RGB values.

Now let’s work out how to write everything to a PNG file.

Π§ΠΈΡ‚Π°ΠΉΡ‚Π΅ Ρ‚Π°ΠΊΠΆΠ΅:  Python any element list another list

A PNG file begins with a header. Let’s define it as a constant:

We will later write it out to our file.

Chunks πŸ”—

The header is followed by a number of chunks. A chunk is a named data block that consists of:

  • a 4-byte length field
  • a 4-byte chunk type field
  • the data
  • a 4-byte checksum

The length field holds the size of the data field, and the chunk type is a special name defined in the PNG spec that represents what kind of data this chunk holds. Next comes the actual data, and finally a checksum.

The checksum is computed from the chunk type and the data using the CRC algorithm. The length field is not included in the checksum.

We will implement a function that writes out a data chunk, but first let’s begin with a function that returns the checksum:

import zlib def get_checksum(chunk_type: bytes, data: bytes) -> int: checksum = zlib.crc32(chunk_type) checksum = zlib.crc32(data, checksum) return checksum

Fortunately we don’t need to implement the CRC algorithm ourselves, as Python conveniently exposes it via the crc32 function from the zlib library. We make two calls to crc32 in order to compute a running checksum on two inputs: the chunk type and the data.

We can now implement a function that can write any type of a PNG chunk to a file:

import struct from typing import BinaryIO def chunk(out: BinaryIO, chunk_type: bytes, data: bytes) -> None: out.write(struct.pack('>I', len(data))) out.write(chunk_type) out.write(data) checksum = get_checksum(chunk_type, data) out.write(struct.pack('>I', checksum))

Note: The PNG format requires us to output integers as big endian, which is what we use struct.pack for. More specifically ‘>I’ indicates a 4-byte big endian unsigned integer.

The Image Header Chunk (IHDR) πŸ”—

The first mandatory chunk we need to write after the PNG header is IHDR (the image header). It has the following format:

  • 4-byte width
  • 4-byte height
  • 1-byte bit depth
  • 1-byte colour type
  • 1-byte compression method
  • 1-byte filter method
  • 1-byte interlace method

For simplicity, we can ignore the compression method, filter method and interlace method, and set them to 0. As for the rest, let’s define a function that generates the data for this chunk.

def make_ihdr(width: int, height: int, bit_depth: int, color_type: int) -> bytes: return struct.pack('>2I5B', width, height, bit_depth, color_type, 0, 0, 0)

Here we use struct.pack to pack the data into a byte string (namely as per the IHDR spec, we write 2 unsigned integers and 5 bytes).

The Data Chunk (IDAT) πŸ”—

After the image header comes the main data chunk. We’ll write a function that coverts our image format to something we can output as part of the chunk.

Π§ΠΈΡ‚Π°ΠΉΡ‚Π΅ Ρ‚Π°ΠΊΠΆΠ΅:  Css display inline hover

We are representing pixel data as a list of pixel lists, where each pixel itself is an RGB triple. We now need to encode this data into scanlines. A scanline is just a continuous row of bytes where every byte holds a colour value from our RGB triples. Every scanline begins with a filter type byte. We are not doing any fancy filtering here so we will use 0 as filter type.

The function to encode the data will then look like this:

def encode_data(img: Image) -> List[int]: ret = [] for row in img: ret.append(0) color_values = [ color_value for pixel in row for color_value in pixel ] ret.extend(color_values) return ret

Note: A more sophisticated implementation could perform different filtering for every scanline, based on the contents of the scanline. This is why every row starts with its own filter type.

The PNG format also requires us to compress the data using the zlib compression method. Here we will again use the Python standard zlib library. Let’s write a function for compression:

def compress_data(data: List[int]) -> bytes: data_bytes = bytearray(data) return zlib.compress(data_bytes)

With these two functions we can generate the data field for the IDAT data chunk:

def make_idat(img: Image) -> bytes: encoded_data = encode_data(img) compressed_data = compress_data(encoded_data) return compressed_data

Note: PNG files can have multiple IDAT chunks, but for demonstration purposes we will only write one.

PNG Output πŸ”—

Let’s put all of this together and write a function to output the whole PNG image. We will start out by outputting the PNG header:

def dump_png(out: BinaryIO, img: Image) -> None: out.write(HEADER) # start by writing the header

Now let’s consolidate all we need for the Image Header IHDR chunk.

The width and the height are just the dimensions of the image. Bit depth refers to the number of bits used to represent each channel (not pixel). In our case, each value in an RGB triple is represented by 1 byte (0-255), so the bit depth will be 8. Colour type describes the colour model of the image. An image defined by RGB triples with no alpha channel has colour type 2.

 assert len(img) > 0 # assume we were not given empty image data width = len(img[0]) height = len(img) bit_depth = 8 # bits per pixel color_type = 2 # pixel is RGB triple ihdr_data = make_ihdr(width, height, bit_depth, color_type) chunk(out, b'IHDR', ihdr_data)

After IHDR comes the data IDAT chunk:

 compressed_data = make_idat(img) chunk(out, b'IDAT', data=compressed_data)

Finally, the PNG format requires us to output an IEND chunk that marks the end of the PNG image. This chunk does not hold any data:

Let’s write a function that actually opens a binary file and writes out the data:

def save_png(img: Image, filename: str) -> None: with open(filename, 'wb') as out: dump_png(out, img)

To conclude the demonstration, let’s generate the checkerboard pattern and call the function above to save it to a file:

width = 100 height = 100 img = generate_checkerboard_pattern(width, height) save_png(img, 'out.png')

Output image

References πŸ”—

Π˜ΡΡ‚ΠΎΡ‡Π½ΠΈΠΊ

Π§ΠΈΡ‚Π°ΠΉΡ‚Π΅ Ρ‚Π°ΠΊΠΆΠ΅:  Python regular expressions symbols

ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° ΠΈ гСнСрация ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ Π² Python. Π‘ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠ° Pillow

ΠžΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ° ΠΈ гСнСрация ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ Π² Python. Π‘ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠ° Pillow

НСрСдко Π½Π°ΠΌ Π½ΡƒΠΆΠ½ΠΎ ΠΈΠ·ΠΌΠ΅Π½ΠΈΡ‚ΡŒ Ρ€Π°Π·ΠΌΠ΅Ρ€ ΠΊΠ°Ρ€Ρ‚ΠΈΠ½ΠΊΠΈ, ΠΊΡ€ΠΎΠΏΠ½ΡƒΡ‚ΡŒ Π΅Π΅, Π½Π°Π»ΠΎΠΆΠΈΡ‚ΡŒ тСкст ΠΈ Ρ‚Π°ΠΊ Π΄Π°Π»Π΅Π΅. ВсС это ΠΌΠΎΠΆΠ½ΠΎ Π°Π²Ρ‚ΠΎΠΌΠ°Ρ‚ΠΈΠ·ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ с ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ Python ΠΈ Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠΈ Pillow.

Установка Pillow

Установка производится Ρ‡Π΅Ρ€Π΅Π· pip3. Π’Π²ΠΎΠ΄ΠΈΠΌ Π² консоль:

Начало Ρ€Π°Π±ΠΎΡ‚Ρ‹ с Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠΎΠΉ

Для Π½Π°Ρ‡Π°Π»Π° Π½Π°ΠΌ Π½ΡƒΠΆΠ½ΠΎ ΠΈΠ½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΡƒ Π² нашСм скриптС:

Π’ ΠΏΠ°ΠΏΠΊΡƒ со скриптом помСстим ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ»ΡŒΠ½ΠΎΠ΅ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅. Π€Π°ΠΉΠ» Π½Π°Π·ΠΎΠ²Π΅ΠΌ test.jpg.

Π’Π΅ΠΏΠ΅Ρ€ΡŒ ΠΎΡ‚ΠΊΡ€ΠΎΠ΅ΠΌ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ Ρ‡Π΅Ρ€Π΅Π· ΠΌΠ΅Ρ‚ΠΎΠ΄ Π² Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠ΅:

from PIL import Image img = Image.open('test.jpg') img.show()

На экранС Ρƒ нас ΠΎΡ‚ΠΎΠ±Ρ€Π°Π·ΠΈΠ»ΠΎΡΡŒ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅, ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠ΅ ΠΌΡ‹ Π΄ΠΎΠ±Π°Π²ΠΈΠ»ΠΈ Π² ΠΏΠ°ΠΏΠΊΡƒ:

Pillow Python

Для просмотра основной ΠΈΠ½Ρ„ΠΎΡ€ΠΌΠ°Ρ†ΠΈΠΈ ΠΎΠ± ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΈ с ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ Pillow ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ ΡΠ»Π΅Π΄ΡƒΡŽΡ‰ΠΈΠ΅ ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹:

from PIL import Image img = Image.open('test.jpg') print(img.format) # ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π° изобраТСния. Π’Ρ‹Π²Π΅Π΄Π΅Ρ‚ 'JPEG' print(img.mode) # ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€ Ρ‚ΠΈΠΏΠ° Ρ†Π²Π΅Ρ‚ΠΎΠ²ΠΎΠ³ΠΎ пространства. Π’Ρ‹Π²Π΅Π΄Π΅Ρ‚ 'RGB' print(img.size) # ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€ Ρ€Π°Π·ΠΌΠ΅Ρ€Π° изобраТСния. Π’Ρ‹Π²Π΅Π΄Π΅Ρ‚ (568, 305) print(img.filename) # ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€ ΠΈΠΌΠ΅Π½ΠΈ Ρ„Π°ΠΉΠ»Π°. Π’Ρ‹Π²Π΅Π΄Π΅Ρ‚ 'test.jpg' r, g, b = img.split() histogram = img.histogram() print(histogram) # ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠΉ RGB изобраТСния. Π’Ρ‹Π²Π΅Π΄Π΅Ρ‚ 1750, 255, 267, 237, 276, 299…

ΠžΠ±Ρ€Π΅Π·ΠΊΠ° ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ

Π’ Pillow Π΅ΡΡ‚ΡŒ ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹ для ΠΊΡ€ΠΎΠΏΠ° (ΠΎΠ±Ρ€Π΅Π·ΠΊΠΈ) ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ. ΠžΠ±Ρ€Π΅ΠΆΠ΅ΠΌ нашС ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ с ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ ΠΌΠ΅Ρ‚ΠΎΠ΄Π° crop(), ΠΏΠ΅Ρ€Π΅Π΄Π°Π² Π² ΠΏΠ°Ρ€Π°ΠΌΠ΅Ρ‚Ρ€Ρ‹ ΠΊΠΎΠΎΡ€Π΄ΠΈΠ½Π°Ρ‚Ρ‹ ΠΎΠ±Ρ€Π΅Π·ΠΊΠΈ:

from PIL import Image img = Image.open('test.jpg') cropped = img.crop((0, 0, 100, 200)) cropped.save('cropped_test.jpg') img = Image.open('cropped_test.jpg') img.show()

ПослС выполнСния Π΄Π°Π½Π½ΠΎΠ³ΠΎ ΠΊΠΎΠ΄Π°, ΠΏΠΎΠ»ΡƒΡ‡ΠΈΠΌ ΡΠ»Π΅Π΄ΡƒΡŽΡ‰Π΅Π΅ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅:

Π€ΠΎΡ‚ΠΎ Π² Pillow Python

ΠŸΠΎΠ²ΠΎΡ€ΠΎΡ‚ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ

Π‘ ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ ΠΌΠ΅Ρ‚ΠΎΠ΄Π° Image.rotate() ΠΌΡ‹ ΠΌΠΎΠΆΠ΅ΠΌ ΠΏΠΎΠ²ΠΎΡ€Π°Ρ‡ΠΈΠ²Π°Ρ‚ΡŒ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ ΠΊΠ°ΠΊ Π½Π°ΠΌ ΡƒΠ³ΠΎΠ΄Π½ΠΎ. Π’ скобках ΡƒΠΊΠ°Π·Ρ‹Π²Π°Π΅ΠΌ количСство градусов, Π½Π° ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠ΅ Π½ΡƒΠΆΠ½ΠΎ ΠΏΠΎΠ²Π΅Ρ€Π½ΡƒΡ‚ΡŒ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅. Рассмотрим Π½Π° ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π΅:

from PIL import Image img = Image.open('test.jpg') rotated = img.rotate(180) rotated.save('rotated_test.jpg') img = Image.open('rotated_test.jpg') img.show()

ΠŸΠΎΠ²ΠΎΡ€ΠΎΡ‚ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ Π² Pillow Python

ΠšΠΎΠ½Π²Π΅Ρ€Ρ‚ΠΈΡ€ΡƒΠ΅ΠΌ ΠΈΠ· JPG Π² PNG с ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ Pillow

Для ΠΊΠΎΠ½Π²Π΅Ρ€Ρ‚Π°Ρ†ΠΈΠΈ Π½Π°ΠΌ понадобится ΠΎΡ‚ΠΊΡ€Ρ‹Ρ‚ΡŒ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ ΠΈ просто ΡΠΎΡ…Ρ€Π°Π½ΠΈΡ‚ΡŒ Π΅Π³ΠΎ Π² Π΄Ρ€ΡƒΠ³ΠΎΠΌ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π΅. Рассмотрим Π½Π° ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π΅:

from PIL import Image img = Image.open('test.jpg') img.save('test_png.png', 'png')

ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ Ρ‚Π°ΠΊΠΎΠ΅ ΠΆΠ΅ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅, Π½ΠΎ Π² Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π΅ PNG.

ИзмСнСниС Ρ€Π°Π·ΠΌΠ΅Ρ€Π° ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ

Π§Ρ‚ΠΎΠ±Ρ‹ ΠΈΠ·ΠΌΠ΅Π½ΠΈΡ‚ΡŒ Ρ€Π°Π·ΠΌΠ΅Ρ€ изобраТСния ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ ΠΌΠ΅Ρ‚ΠΎΠ΄ resize(). Рассмотрим это Π½Π° ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π΅:

from PIL import Image img = Image.open('test.jpg') img = img.resize((170, 100), Image.ANTIALIAS) img.save('test_text.jpg') img = Image.open('test_text.jpg') img.show()

ΠšΡ€ΠΎΠΏ Π² Pillow Python

ПишСм тСкст Π½Π° изобраТСниях

Для налоТСния тСкста Π½Π° ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ Π² Pillow сущСствуСт ΠΌΠ΅Ρ‚ΠΎΠ΄ text(), Π½ΠΎ для Π½Π°Ρ‡Π°Π»Π° Π½ΡƒΠΆΠ½ΠΎ Π΄ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ ΡˆΡ€ΠΈΡ„Ρ‚. Рассмотрим Π½Π° ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π΅:

from PIL import Image, ImageDraw, ImageFont img = Image.open('test.jpg') font = ImageFont.truetype("arial.ttf", size=20) idraw = ImageDraw.Draw(img) idraw.text((25, 25), 'TEST test TeSt', font=font) img.save('test_text.jpg') img = Image.open('test_text.jpg') img.show()

ΠŸΠΎΠ»ΡƒΡ‡ΠΈΠΌ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ с тСкстом:

ВСкст Π½Π° ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΈ - Pillow Python

ГСнСрация пустого изобраТСния

Для создания пустого холста (изобраТСния) ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅ΠΌ ΠΌΠ΅Ρ‚ΠΎΠ΄ Image.new(). Рассмотрим Π½Π° ΠΏΡ€ΠΈΠΌΠ΅Ρ€Π΅:

from PIL import Image, ImageDraw, ImageFont img = Image.new('RGB', (200, 200), 'black') img.save('test1.jpg') img = Image.open('test1.jpg') img.show()

Π Π°Π±ΠΎΡ‚Π° с Pillow Python

Π’Π΅ΠΏΠ΅Ρ€ΡŒ Π΄Π°Π²Π°ΠΉΡ‚Π΅ нарисуСм Π½Π° этом ΠΊΠ²Π°Π΄Ρ€Π°Ρ‚Π΅ Π±Π΅Π»Ρ‹ΠΉ ΠΏΡ€ΡΠΌΠΎΡƒΠ³ΠΎΠ»ΡŒΠ½ΠΈΠΊ:

from PIL import Image, ImageDraw, ImageFont img = Image.new('RGB', (200, 200), 'black') idraw = ImageDraw.Draw(img) idraw.rectangle((0, 0, 100, 100), fill='white') img.save('test1.jpg') img = Image.open('test1.jpg') img.show()

РисованиС в Pillow Python

Π’Ρ‹Π²ΠΎΠ΄

ΠœΡ‹ Ρ€Π°Π·ΠΎΠ±Ρ€Π°Π»ΠΈ основныС ΠΌΠ΅Ρ‚ΠΎΠ΄Ρ‹ Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠΈ Pillow Π² Python: Π½Π°ΡƒΡ‡ΠΈΠ»ΠΈΡΡŒ ΠΏΠΈΡΠ°Ρ‚ΡŒ тСкст Π½Π° изобраТСниях, ΠΈΠ·ΠΌΠ΅Π½ΡΡ‚ΡŒ Ρ€Π°Π·ΠΌΠ΅Ρ€, ΠΏΠΎΠ²ΠΎΡ€Π°Ρ‡ΠΈΠ²Π°Ρ‚ΡŒ ΠΈΡ… ΠΈ Π΄Π°ΠΆΠ΅ ΠΎΠ±Ρ€Π΅Π·Π°Ρ‚ΡŒ.

НадСюсь, ΡΡ‚Π°Ρ‚ΡŒΡ Π±Ρ‹Π»Π° ΠΏΠΎΠ»Π΅Π·Π½Π° для вас. Π£Π΄Π°Ρ‡ΠΈ!

Π˜ΡΡ‚ΠΎΡ‡Π½ΠΈΠΊ

ΠžΡ†Π΅Π½ΠΈΡ‚Π΅ ΡΡ‚Π°Ρ‚ΡŒΡŽ