Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

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

Оптимизация web-изображений, котики и wasm

Polina Gurtovaya

November 30, 2019
Tweet

More Decks by Polina Gurtovaya

Other Decks in Programming

Transcript

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

    View Slide

  2. 2

    View Slide

  3. 3

    View Slide

  4. 4

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  19. Y Cb Cr
    19

    View Slide

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

    View Slide

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

    View Slide

  22. original image: 400 x 400
    22

    devicePixelRatio: 2

    View Slide

  23. original image: 400 x 400
    23

    devicePixelRatio: 2

    View Slide

  24. Upscaling
    24

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  28. 28
    Encoder

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  34. 34

    View Slide

  35. IHDR chunk
    width
    height
    bitDepth
    colorType
    35

    View Slide

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

    View Slide

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

    View Slide

  38. 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);
    // ...

    View Slide

  39. 39

    View Slide

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

    View Slide

  41. 41

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  46. Применяем магическое преобразование
    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]]

    View Slide

  47. Применяем магическое преобразование
    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]]

    View Slide

  48. 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]]

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  52. VP8X chunk
    alpha
    EXIF
    animation
    width
    height
    52

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  56. А можем еще лучше?
    ffmpeg -i file.png \
    -map_metadata -1 \
    -an -c:v libaom-av1 -crf 24 \
    -b:v 0 -an -vframe 1 -strict experimental result.mp4
    muted
    src={video}
    type="video/mp4; codecs=av01.0.05M.08”
    />
    56

    View Slide

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

    View Slide

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

    View Slide

  59. imgproxy
    59
    imgproxy.net

    View Slide

  60. 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) => `

    srcset="${getUrl(imgproxyUrl, src, size, 1, '@webp')} @1x,
    ${getUrl(imgproxyUrl, src, size, 2, '@webp')} @2x"
    type="image/webp"
    />
    srcset="${getUrl(imgproxyUrl, src, size, 1)} @1x,
    ${getUrl(imgproxyUrl, src, size, 2)} @2x"
    alt="a yummy cupcake" />
    `

    View Slide

  61. Squoosh
    61

    View Slide

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

    View Slide

  63. 63

    View Slide

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

    View Slide

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

    View Slide