Slide 1

Slide 1 text

(не)идеальные картинки и другая пиксельная магия

Slide 2

Slide 2 text

2

Slide 3

Slide 3 text

3

Slide 4

Slide 4 text

4

Slide 5

Slide 5 text

Готовить графический контент нелегко

Slide 6

Slide 6 text

Показать пользователю картинку нужного качества как можно быстрее 6

Slide 7

Slide 7 text

Шаг 1: Транспорт

Slide 8

Slide 8 text

Вы используете HTTP/2 Вы используете CDN (например, cloudflare) Все ок со сжатием (gzip / brotli) Все ок с кешированием 8 Убедитесь, что:

Slide 9

Slide 9 text

9 Шаг 2: Понимание

Slide 10

Slide 10 text

10 Картинка выражает какую-то идею, воспринимается в контексте и подразумевает некую реакцию пользователя

Slide 11

Slide 11 text

Качество зависит от контекста Картинка это основной контент — качество повыше Картинка это вспомогательный контент — качество пониже Иногда картинку нужно заменить интерактивным элементом 11

Slide 12

Slide 12 text

12 Шаг 3: Выбираем правильный формат

Slide 13

Slide 13 text

13 Когда SVG заходят хорошо Часть изображения можно представить как набор геометрических примитивов Есть интерактивность Нужно цеплять маркеры к фону Нужно много размеров 13

Slide 14

Slide 14 text

В остальных случаях пробуйте все подходящие форматы и выбирайте лучший 14

Slide 15

Slide 15 text

svgo/svgr/svgomg 15 Оптимизируйте SVG

Slide 16

Slide 16 text

Время растровой графики 16

Slide 17

Slide 17 text

Что такое пиксель ? 17 abstract pixel hardware pixel CSS pixel

Slide 18

Slide 18 text

18 Несколько чисел, хранящие информацию о цвете и прозрачности. color model — то как хранится цвет Abstract pixel R G B Y Cb Cr — luma (Y) + chroma(Cb Cr).

Slide 19

Slide 19 text

Y Cb Cr 19

Slide 20

Slide 20 text

CSS pixel Абсолютная единица измерения Сколько-то миллиметров Сколько именно миллиметров зависит от устройства 20

Slide 21

Slide 21 text

Device pixel ratio СSS pixel height / hardware pixel height 21

Slide 22

Slide 22 text

original image: 400 x 400 22 devicePixelRatio: 2

Slide 23

Slide 23 text

original image: 400 x 400 23 devicePixelRatio: 2

Slide 24

Slide 24 text

Upscaling 24

Slide 25

Slide 25 text

Подбирайте картинку под контейнер Размер картинки = размер контейнера dpr 25

Slide 26

Slide 26 text

Зачем cжимать картинки? Картинка 400 x 400 3 канала, 1 byte на канал 3 400 400 1 byte = 480Kb 26

Slide 27

Slide 27 text

Два способа сжатия Losseless (GIF, PNG, WEBP) Lossy (JPG, WEBP, …) 27

Slide 28

Slide 28 text

28 Encoder

Slide 29

Slide 29 text

GIF Intraframe — есть Interframe— нет Алгоритм сжатия не очень крутой (LZW) Losseless 256 цветов (любых) 29

Slide 30

Slide 30 text

РNG 30 black-rect-sketch.png 506 Байт black-rect-opti.png 91 Байт

Slide 31

Slide 31 text

31 Чтобы разобраться в чём дело, давайте напишем свой PNG-декодер

Slide 32

Slide 32 text

Анатомия PNG контейнера 32 splitToChunks(bytesFromFile)

Slide 33

Slide 33 text

33 const reader = new FileReader(); reader.onload = event => console.log(splitToChunks(event.target.result)) { const file = input.current.files[0]; reader.current.readAsArrayBuffer(file); }} />

Slide 34

Slide 34 text

34

Slide 35

Slide 35 text

IHDR chunk width height bitDepth colorType 35

Slide 36

Slide 36 text

Сравниваем 2 черных квадрата 36

Slide 37

Slide 37 text

Допиливаем декодер 37 #include "emscripten.h" #include "zlib.h" EMSCRIPTEN_KEEPALIVE int decompress(/* ... */) { //... inflate(/* ... */); //... return pointer } emcc -O3 -s WASM=1 … decompressor.wasm

Slide 38

Slide 38 text

38 import decompressJS from './wasm/decompress'; import decompressModule from './wasm/decompress.wasm'; mod.destroyAll(inputPointer, resultPointer); // ... somehow run wasm and create 'mod' object const inputPointer = mod.createBuffer(); mod.HEAP8.set(getIDAT(chunkData), inputPointer); const resultPointer = mod.decompress(/* ...*/); const decompressedData = new Uint8Array( mod.HEAP8.buffer, resultPointer, size ); const imgData = processDecopressedData(decompressedData); canvas.current.getContext('2d').putImageData(imgData); // ...

Slide 39

Slide 39 text

39

Slide 40

Slide 40 text

40 Еще немножко трюков 40 Подключаем OpenCV Натравливаем OpenCV на наши байтики faceCascade.detectMultiScale(src, dst, 1.3, 3, 0);

Slide 41

Slide 41 text

41

Slide 42

Slide 42 text

PNG многое умеет Режим GrayScale Может грузиться постепенно 42

Slide 43

Slide 43 text

JPEG Сжатие с потерями… 43

Slide 44

Slide 44 text

Разбиваем изображение на блоки 8 x 8 44

Slide 45

Slide 45 text

Y Cb Cr. Каждый канал отдельно 45

Slide 46

Slide 46 text

Применяем магическое преобразование 46 [[169 171 174 177 179 179 177 177] [171 173 176 179 180 180 178 177] [174 176 179 181 182 182 180 179] [176 178 180 183 184 183 181 180] [174 180 186 189 186 180 176 173] [182 185 187 188 186 182 178 176] [188 187 186 187 187 184 180 176] [185 185 186 189 190 188 181 175]] [[420.6 1.2 -21.9 1.5 0.1 -0.4 -0.3 -0.6] [-25.2 -17.6 3.7 -2.9 0.1 -0.4 0.7 -0. ] [ -1.7 -0.8 2.8 4.9 -0. 0. 0.6 -0.2] [ -3.8 4.1 -1.9 -3.4 0.4 -0.3 -0.4 0.2] [ -1.9 -2.7 -4. -0.2 0.1 -0. -0.1 -0.1] [ 2.2 -0.9 5.8 2. 0.5 -0.3 0. 0.2] [ 0.8 -0.7 -0.4 -0.5 -0.4 -0.3 0.5 -0.1] [ -1.4 1.8 -2.5 -1.1 -0.2 0.3 -0.1 0.4]]

Slide 47

Slide 47 text

Применяем магическое преобразование 47 [[420.6 1.2 -21.9 1.5 0.1 -0.4 -0.3 -0.6] [-25.2 -17.6 3.7 -2.9 0.1 -0.4 0.7 -0. ] [ -1.7 -0.8 2.8 4.9 -0. 0. 0.6 -0.2] [ -3.8 4.1 -1.9 -3.4 0.4 -0.3 -0.4 0.2] [ -1.9 -2.7 -4. -0.2 0.1 -0. -0.1 -0.1] [ 2.2 -0.9 5.8 2. 0.5 -0.3 0. 0.2] [ 0.8 -0.7 -0.4 -0.5 -0.4 -0.3 0.5 -0.1] [ -1.4 1.8 -2.5 -1.1 -0.2 0.3 -0.1 0.4]]

Slide 48

Slide 48 text

Quantization 48 [[420.6 1.2 -21.9 1.5 0.1 -0.4 -0.3 -0.6] [-25.2 -17.6 3.7 -2.9 0.1 -0.4 0.7 -0. ] [ -1.7 -0.8 2.8 4.9 -0. 0. 0.6 -0.2] [ -3.8 4.1 -1.9 -3.4 0.4 -0.3 -0.4 0.2] [ -1.9 -2.7 -4. -0.2 0.1 -0. -0.1 -0.1] [ 2.2 -0.9 5.8 2. 0.5 -0.3 0. 0.2] [ 0.8 -0.7 -0.4 -0.5 -0.4 -0.3 0.5 -0.1] [ -1.4 1.8 -2.5 -1.1 -0.2 0.3 -0.1 0.4]] [[53 0 -2 0 0 0 0 0] [-2 -1 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0] [ 0 0 0 0 0 0 0 0]] [[ 8 8 8 9 13 19 28 43] [ 8 9 10 14 17 20 27 38] [ 8 10 12 16 22 31 46 68] [ 9 14 16 20 27 37 53 78] [ 13 17 22 27 35 47 66 95] [ 19 20 31 37 47 62 85 119] [ 28 27 46 53 66 85 113 156] [ 43 38 68 78 95 119 156 209]]

Slide 49

Slide 49 text

Progressive JPEG Мы просто присылаем часть табличек для всех блоков 49

Slide 50

Slide 50 text

WebP WebP это куча форматов в одном контейнере Может быть lossless и lossy Поддерживает прозрачность Может быть lossy для цвета и lossless для прозрачности Не может грузиться постепенно 50

Slide 51

Slide 51 text

Lossless и lossy WebP Lossy — основано на сжатии VP8 кодека Lossless — запилили отдельно 51

Slide 52

Slide 52 text

VP8X chunk alpha EXIF animation width height 52

Slide 53

Slide 53 text

Особенности lossy WebP Lossless алгоритм эффективней чем тот, что использует JPEG Adaptive quantization 53

Slide 54

Slide 54 text

Как работают современные video-encoders Разбивают картинку на блоки Предсказывают новые блоки на основе предыдущих 54

Slide 55

Slide 55 text

А можем еще лучше? WebP использует компрессию из VP8 видео кодека… Что если взять видео кодек посовременнее? 55

Slide 56

Slide 56 text

А можем еще лучше? ffmpeg -i file.png \ -map_metadata -1 \ -an -c:v libaom-av1 -crf 24 \ -b:v 0 -an -vframe 1 -strict experimental result.mp4

Slide 57

Slide 57 text

Как использовать AV1 57 evl.ms/blog/better-web-video-with-av1-codec

Slide 58

Slide 58 text

58 Инструменты

Slide 59

Slide 59 text

imgproxy 59 imgproxy.net

Slide 60

Slide 60 text

60 imgproxy с const getUrl = (imgproxyUrl, src, size, dpr, extension) => `${imgproxyUrl}/fit/${dpr * size}/${dpr * size}/sm/0/plain/${src}${extension || ''}`; const createPicture = (imgproxyUrl, src, size) => ` a yummy cupcake `

Slide 61

Slide 61 text

Squoosh 61

Slide 62

Slide 62 text

Еще полезные инструменты 62 libvips optipng libwebp mozjpeg ImageMagic ImageOptim

Slide 63

Slide 63 text

63

Slide 64

Slide 64 text

64 evl.ms/blog/images-done-right-web-graphics-good-to-the-last-byte-optimization-techniques

Slide 65

Slide 65 text

65 Спасибо! @polina_gurtovaya @pgurtovaya [email protected] 65 @evilmartians @evilmartians_ru evilmartians.com