Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
(не)идеальные картинки и другая пиксельная маги...
Search
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
Polina Gurtovaya
November 30, 2019
Programming
180
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
(не)идеальные картинки и другая пиксельная магия (ufadevconf))
Оптимизация web-изображений, котики и wasm
Polina Gurtovaya
November 30, 2019
More Decks by Polina Gurtovaya
See All by Polina Gurtovaya
Не учите алгоритмы
hellsquirrel
1
1.1k
Давайте все заблокируем
hellsquirrel
0
370
Wasmысле?
hellsquirrel
0
280
Магия декларативныx схем.
hellsquirrel
0
400
ML for HolyJS
hellsquirrel
0
200
Идеальный способ заблюрить белочку
hellsquirrel
0
200
ML/DL на фронте
hellsquirrel
0
250
InsertableStreams
hellsquirrel
0
120
WebRTC-404
hellsquirrel
0
580
Other Decks in Programming
See All in Programming
仕様駆動開発による爆速プロダクト開発 / Bakusoku Spec Driven Development
kobakei
0
120
AI に Inclusive UI を書かせよう — Design Rules Skill で Compose UI を作り直す
theoriatec2024
1
490
AI Agent時代のリアーキテクチャ戦略と実践
hokaccha
9
4.7k
App Storeの外へ──日本のiOSサイドローディング入門 for iOSDC Japan 2026
yuukiw00w
0
220
LL言語やWebフレームワークのPostgreSQL対応 〜DBの機能がユーザーに届くまで〜
kentaroutakeda
1
150
Family mrubyの進捗
kishima
1
120
Starting & Sustaining Code-Based E2E Testing for Non-Coding QA Teams( #jasstniigata )
teyamagu
PRO
1
330
モジュールの視点からSwiftを読み解く #iosdc
s_shimotori
0
180
個人開発基盤をまるごとCloudflareに引っ越して爆速で総合的体験を向上させた話
tinykitten
0
190
AI × TiDD / 2026.09.05 Redmine 大阪
tokudiro
1
170
AIは賢い。でも実行環境は? CLIおじさんがAI時代に伝えたいこと ~ CLIおじさんがAI時代に伝えたいこと ~
curekoshimizu
1
240
そのリトライ、死んだコネクションを使い回していませんか ── GoのHTTPクライアントとHTTP/2を実プロダクト障害から学び直す
myus4a
0
140
Featured
See All Featured
How to Think Like a Performance Engineer
csswizardry
28
2.8k
Large-scale JavaScript Application Architecture
addyosmani
515
110k
Conquering PDFs: document understanding beyond plain text
inesmontani
PRO
4
3.1k
Taking LLMs out of the black box: A practical guide to human-in-the-loop distillation
inesmontani
PRO
3
2.4k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
333
23k
Tell your own story through comics
letsgokoyo
1
1.1k
Fashionably flexible responsive web design (full day workshop)
malarkey
409
67k
Marketing Yourself as an Engineer | Alaka | Gurzu
gurzu
0
300
Effective software design: The role of men in debugging patriarchy in IT @ Voxxed Days AMS
baasie
1
530
Stop Working from a Prison Cell
hatefulcrawdad
274
21k
Believing is Seeing
oripsolob
1
220
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
10
1.3k
Transcript
(не)идеальные картинки и другая пиксельная магия
2
3
4
Готовить графический контент нелегко
Показать пользователю картинку нужного качества как можно быстрее 6
Шаг 1: Транспорт
Вы используете HTTP/2 Вы используете CDN (например, cloudflare) Все ок
со сжатием (gzip / brotli) Все ок с кешированием 8 Убедитесь, что:
9 Шаг 2: Понимание
10 Картинка выражает какую-то идею, воспринимается в контексте и подразумевает
некую реакцию пользователя
Качество зависит от контекста Картинка это основной контент — качество
повыше Картинка это вспомогательный контент — качество пониже Иногда картинку нужно заменить интерактивным элементом 11
12 Шаг 3: Выбираем правильный формат
13 Когда SVG заходят хорошо Часть изображения можно представить как
набор геометрических примитивов Есть интерактивность Нужно цеплять маркеры к фону Нужно много размеров 13
В остальных случаях пробуйте все подходящие форматы и выбирайте лучший
14
svgo/svgr/svgomg 15 Оптимизируйте SVG
Время растровой графики 16
Что такое пиксель ? 17 abstract pixel hardware pixel CSS
pixel
18 Несколько чисел, хранящие информацию о цвете и прозрачности. color
model — то как хранится цвет Abstract pixel R G B Y Cb Cr — luma (Y) + chroma(Cb Cr).
Y Cb Cr 19
CSS pixel Абсолютная единица измерения Сколько-то миллиметров Сколько именно миллиметров
зависит от устройства 20
Device pixel ratio СSS pixel height / hardware pixel height
21
original image: 400 x 400 22 <img src="cupcake.jpg" style="width:200px" >
devicePixelRatio: 2
original image: 400 x 400 23 <img src="cupcake.jpg" style="width:400px" >
devicePixelRatio: 2
Upscaling 24
Подбирайте картинку под контейнер Размер картинки = размер контейнера dpr
25
Зачем cжимать картинки? Картинка 400 x 400 3 канала, 1
byte на канал 3 400 400 1 byte = 480Kb 26
Два способа сжатия Losseless (GIF, PNG, WEBP) Lossy (JPG, WEBP,
…) 27
28 Encoder
GIF Intraframe — есть Interframe— нет Алгоритм сжатия не очень
крутой (LZW) Losseless 256 цветов (любых) 29
РNG 30 black-rect-sketch.png 506 Байт black-rect-opti.png 91 Байт
31 Чтобы разобраться в чём дело, давайте напишем свой PNG-декодер
Анатомия PNG контейнера 32 splitToChunks(bytesFromFile)
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
IHDR chunk width height bitDepth colorType 35
Сравниваем 2 черных квадрата 36
Допиливаем декодер 37 #include "emscripten.h" #include "zlib.h" EMSCRIPTEN_KEEPALIVE int decompress(/*
... */) { //... inflate(/* ... */); //... return pointer } emcc -O3 -s WASM=1 … decompressor.wasm
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
40 Еще немножко трюков 40 Подключаем OpenCV Натравливаем OpenCV на
наши байтики faceCascade.detectMultiScale(src, dst, 1.3, 3, 0);
41
PNG многое умеет Режим GrayScale Может грузиться постепенно 42
JPEG Сжатие с потерями… 43
Разбиваем изображение на блоки 8 x 8 44
Y Cb Cr. Каждый канал отдельно 45
Применяем магическое преобразование 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 [[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]]
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]]
Progressive JPEG Мы просто присылаем часть табличек для всех блоков
49
WebP WebP это куча форматов в одном контейнере Может быть
lossless и lossy Поддерживает прозрачность Может быть lossy для цвета и lossless для прозрачности Не может грузиться постепенно 50
Lossless и lossy WebP Lossy — основано на сжатии VP8
кодека Lossless — запилили отдельно 51
VP8X chunk alpha EXIF animation width height 52
Особенности lossy WebP Lossless алгоритм эффективней чем тот, что использует
JPEG Adaptive quantization 53
Как работают современные video-encoders Разбивают картинку на блоки Предсказывают новые
блоки на основе предыдущих 54
А можем еще лучше? WebP использует компрессию из VP8 видео
кодека… Что если взять видео кодек посовременнее? 55
А можем еще лучше? 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
Как использовать AV1 57 evl.ms/blog/better-web-video-with-av1-codec
58 Инструменты
imgproxy 59 imgproxy.net
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>`
Squoosh 61
Еще полезные инструменты 62 libvips optipng libwebp mozjpeg ImageMagic ImageOptim
63
64 evl.ms/blog/images-done-right-web-graphics-good-to-the-last-byte-optimization-techniques
65 Спасибо! @polina_gurtovaya @pgurtovaya
[email protected]
65 @evilmartians @evilmartians_ru evilmartians.com