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

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

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

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

C27317d1ebe4e4a01504acab9d87fe61?s=128

Polina Gurtovaya

November 30, 2019
Tweet

Transcript

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

  2. 2

  3. 3

  4. 4

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

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

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

  8. Вы используете HTTP/2 Вы используете CDN (например, cloudflare) Все ок

    со сжатием (gzip / brotli) Все ок с кешированием 8 Убедитесь, что:
  9. 9 Шаг 2: Понимание

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

    некую реакцию пользователя
  11. Качество зависит от контекста Картинка это основной контент — качество

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

  13. 13 Когда SVG заходят хорошо Часть изображения можно представить как

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

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

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

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

    pixel
  18. 18 Несколько чисел, хранящие информацию о цвете и прозрачности. color

    model — то как хранится цвет Abstract pixel R G B Y Cb Cr — luma (Y) + chroma(Cb Cr).
  19. Y Cb Cr 19

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

    зависит от устройства 20
  21. Device pixel ratio СSS pixel height / hardware pixel height

    21
  22. original image: 400 x 400 22 <img src="cupcake.jpg" style="width:200px" >

    devicePixelRatio: 2
  23. original image: 400 x 400 23 <img src="cupcake.jpg" style="width:400px" >

    devicePixelRatio: 2
  24. Upscaling 24

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

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

    byte на канал 3 400 400 1 byte = 480Kb 26
  27. Два способа сжатия Losseless (GIF, PNG, WEBP) Lossy (JPG, WEBP,

    …) 27
  28. 28 Encoder

  29. GIF Intraframe — есть Interframe— нет Алгоритм сжатия не очень

    крутой (LZW) Losseless 256 цветов (любых) 29
  30. РNG 30 black-rect-sketch.png 506 Байт black-rect-opti.png 91 Байт

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

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

  33. 33 const reader = new FileReader(); reader.onload = event =>

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

  35. IHDR chunk width height bitDepth colorType 35

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

  37. Допиливаем декодер 37 #include "emscripten.h" #include "zlib.h" EMSCRIPTEN_KEEPALIVE int decompress(/*

    ... */) { //... inflate(/* ... */); //... return pointer } emcc -O3 -s WASM=1 … decompressor.wasm
  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); // ...
  39. 39

  40. 40 Еще немножко трюков 40 Подключаем OpenCV Натравливаем OpenCV на

    наши байтики faceCascade.detectMultiScale(src, dst, 1.3, 3, 0);
  41. 41

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

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

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

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

  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]]
  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]]
  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]]
  49. Progressive JPEG Мы просто присылаем часть табличек для всех блоков

    49
  50. WebP WebP это куча форматов в одном контейнере Может быть

    lossless и lossy Поддерживает прозрачность Может быть lossy для цвета и lossless для прозрачности Не может грузиться постепенно 50
  51. Lossless и lossy WebP Lossy — основано на сжатии VP8

    кодека Lossless — запилили отдельно 51
  52. VP8X chunk alpha EXIF animation width height 52

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

    JPEG Adaptive quantization 53
  54. Как работают современные video-encoders Разбивают картинку на блоки Предсказывают новые

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

    кодека… Что если взять видео кодек посовременнее? 55
  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 <video muted src={video} type="video/mp4; codecs=av01.0.05M.08” /> 56
  57. Как использовать AV1 57 evl.ms/blog/better-web-video-with-av1-codec

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

  59. imgproxy 59 imgproxy.net

  60. 60 imgproxy с <picture> const getUrl = (imgproxyUrl, src, size,

    dpr, extension) => `${imgproxyUrl}/fit/${dpr * size}/${dpr * size}/sm/0/plain/${src}${extension || ''}`; const createPicture = (imgproxyUrl, src, size) => ` <picture> <source srcset="${getUrl(imgproxyUrl, src, size, 1, '@webp')} @1x, ${getUrl(imgproxyUrl, src, size, 2, '@webp')} @2x" type="image/webp" /> <img src="${src}" srcset="${getUrl(imgproxyUrl, src, size, 1)} @1x, ${getUrl(imgproxyUrl, src, size, 2)} @2x" alt="a yummy cupcake" /> </picture>`
  61. Squoosh 61

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

  63. 63

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

  65. 65 Спасибо! @polina_gurtovaya @pgurtovaya hellsquirrel@evl.ms 65 @evilmartians @evilmartians_ru evilmartians.com