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

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

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

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

Polina Gurtovaya

November 30, 2019
Tweet

More Decks by Polina Gurtovaya

Other Decks in Programming

Transcript

  1. 2

  2. 3

  3. 4

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

    со сжатием (gzip / brotli) Все ок с кешированием 8 Убедитесь, что:
  5. Качество зависит от контекста Картинка это основной контент — качество

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

    набор геометрических примитивов Есть интерактивность Нужно цеплять маркеры к фону Нужно много размеров 13
  7. 18 Несколько чисел, хранящие информацию о цвете и прозрачности. color

    model — то как хранится цвет Abstract pixel R G B Y Cb Cr — luma (Y) + chroma(Cb Cr).
  8. GIF Intraframe — есть Interframe— нет Алгоритм сжатия не очень

    крутой (LZW) Losseless 256 цветов (любых) 29
  9. 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); }} />
  10. 34

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

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

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

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

  16. Применяем магическое преобразование 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]]
  17. Применяем магическое преобразование 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]]
  18. 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]]
  19. WebP WebP это куча форматов в одном контейнере Может быть

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

    кодека Lossless — запилили отдельно 51
  21. А можем еще лучше? WebP использует компрессию из VP8 видео

    кодека… Что если взять видео кодек посовременнее? 55
  22. А можем еще лучше? 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
  23. 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>`
  24. 63