惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

TaoSecurity Blog
TaoSecurity Blog
Jina AI
Jina AI
雷峰网
雷峰网
月光博客
月光博客
The GitHub Blog
The GitHub Blog
WordPress大学
WordPress大学
B
Blog RSS Feed
美团技术团队
C
CXSECURITY Database RSS Feed - CXSecurity.com
小众软件
小众软件
Security Latest
Security Latest
Microsoft Azure Blog
Microsoft Azure Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
C
Cybersecurity and Infrastructure Security Agency CISA
Last Week in AI
Last Week in AI
A
Arctic Wolf
Latest news
Latest news
Attack and Defense Labs
Attack and Defense Labs
I
Intezer
F
Fortinet All Blogs
罗磊的独立博客
MongoDB | Blog
MongoDB | Blog
Webroot Blog
Webroot Blog
S
Secure Thoughts
Help Net Security
Help Net Security
Apple Machine Learning Research
Apple Machine Learning Research
博客园_首页
V
Visual Studio Blog
P
Proofpoint News Feed
博客园 - 【当耐特】
P
Privacy International News Feed
V
Vulnerabilities – Threatpost
Stack Overflow Blog
Stack Overflow Blog
Know Your Adversary
Know Your Adversary
云风的 BLOG
云风的 BLOG
Hacker News: Ask HN
Hacker News: Ask HN
L
LINUX DO - 最新话题
H
Help Net Security
爱范儿
爱范儿
酷 壳 – CoolShell
酷 壳 – CoolShell
S
SegmentFault 最新的问题
Forbes - Security
Forbes - Security
T
Tailwind CSS Blog
量子位
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
T
Tenable Blog
Cloudbric
Cloudbric
N
News and Events Feed by Topic
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Hugging Face - Blog
Hugging Face - Blog

Homepage on Yihui Xie | 谢益辉

Bye, Stack Overflow - Yihui Xie | 谢益辉 Converting testthat Tests to testit - Yihui Xie | 谢益辉 Reflections on AI-assisted Programming - Yihui Xie | 谢益辉 Preliminary Support for Typst in knitr - Yihui Xie | 谢益辉 R.I.P., Tomas Kalibera - Yihui Xie | 谢益辉 An Introduction to xfun - Yihui Xie | 谢益辉 The Surprising Slowness of `textConnection()` in R - Yihui Xie | 谢益辉 A CDN-backed CTAN Mirror: `tlnet.yihui.org` - Yihui Xie | 谢益辉 Announcing TinyTeX Binaries for arm64 and musl-based Linux - Yihui Xie | 谢益辉 TinyTeX on macOS: No More Messing with `/usr/local/bin` - Yihui Xie | 谢益辉 R.I.P., John Fox - Yihui Xie | 谢益辉 R.I.P., Fritz Leisch - Yihui Xie | 谢益辉 Bye, Hex Stickers - Yihui Xie | 谢益辉 Navigating CRAN's Reverse Dependency Check Logs - Yihui Xie | 谢益辉 Viewing Nested Lists with `xfun::tabset()` - Yihui Xie | 谢益辉
tinyimg: An R Package for Compressing Images - Yihui Xie | 谢益辉
Yihui Xie · 2026-03-29 · via Homepage on Yihui Xie | 谢益辉

Last month, @bastistician opened an issue on the litedown repo pointing out that knitr has a hook_pngquant() function for compressing PNG plots from code chunks, but litedown lacks such a feature. He included a reasonable workaround—calling system2("pngquant", ...) with litedown::get_context("plot_files") in a chunk at the end of the vignette. It shrank his vignette from 80 KB to 54 KB, which is a 33% reduction. Not bad.

The catch, of course, is that it requires pngquant to be installed on the system. For R users, installing a system binary is more friction than it sounds: it is brew install pngquant on macOS, a separate package manager invocation on Linux, and hunting down a standalone executable on Windows. If you maintain a package that others will build, you are now asking all of them to do this—for every machine they use. By contrast, install.packages("tinyimg") works the same way everywhere, which is the kind of simplicity that makes a tool actually get used.

This is why I created tinyimg.

What it does

tinyimg compresses PNG files using two Rust crates bundled inside the package itself—no external tools needed:

  • oxipng for lossless optimization: it re-encodes the PNG more efficiently without changing a single pixel.
  • exoquant for lossy palette reduction: the image can be quantized down to at most 256 colors before handing it to oxipng for further loseless compression.

The main function is tinypng(). At its simplest:

library(tinyimg)

tmp = tempfile(fileext = ".png")
png(tmp, width = 800, height = 600)
plot(1:100, main = "A perfectly ordinary plot")
dev.off()

tinypng(tmp)  # optimize in-place

You will see output like:

/tmp/Rtmpxyz/file123.png -> /tmp/Rtmpxyz/file123.png | 52.8 KB -> 39.5 KB (-25.2%)

That is the lossless path at the default optimization level of 2. If you want to go harder, you can set level anywhere from 0 (fast, minimal gain) to 6 (slow, maximum compression). Level 2 usually captures most of the low-hanging fruit, whereas level 6 typically squeezes out only a few more percentage points while taking 10–15x longer.1

Lossy compression

If lossless is not enough, you can enable lossy palette reduction via the lossy argument. The value is a color-difference threshold in CIELAB space—specifically $\Delta E_{76}$. A threshold of 2.3 is the traditional “just noticeable difference” (JND), meaning the color error introduced by the quantization is theoretically imperceptible to the human eye:

tinypng(tmp, paste0(tmp, "-lossy.png"), lossy = 2.3)

In practice, for the kind of statistical graphics R produces—flat backgrounds, solid colored points, clean lines—a lossy = 2.3 compression is nearly indistinguishable from the original while cutting file sizes by 50–70%. Aggressive settings like lossy = 32 can push reduction past 80%, but at that point you may start to see banding in smooth gradients.2

To be honest, I am not sure 2.3 is a practically reasonable general-purpose threshold. From my own experiments with the benchmark plots, it feels rather conservative—the compressed images at somewhat higher values still look perfectly fine to me. Users will likely need to experiment on their own images to find the sweet spot. If you do systematic experiments and arrive at better guidance on what thresholds work well for typical R graphics, I would love to hear about your findings.

Using it with litedown

To answer the original issue: if you use litedown and want to compress all the plots in your vignette, just add a chunk like this at the end (assuming you use the PNG format for all plots):

if (requireNamespace("tinyimg"))
  tinyimg::tinypng(litedown::get_context("plot_files"), lossy = 2.3)

One line. No system2(), no pngquant on the PATH, no CRAN environment concerns. The lossy argument is optional—leave it out if you only want lossless compression.

For knitr users, the situation is similar. You can pass the output directory to tinypng() directly:

# if the chunk option fig.path is a dir used for all plots
tinypng(knitr::opts_chunk$get("fig.path"))

# or perhaps you can optimize the current working directory
tinypng(".")

It will recursively find and compress every PNG in the directory.

Installation

Since tinyimg uses Rust internally, building from source requires Cargo. The easiest route is to install a precompiled binary from CRAN or r-universe, which handles the Rust compilation on its end:

# CRAN version
install.packages("tinyimg", repos = "https://cloud.r-project.org")

# dev version
install.packages("tinyimg", repos = "https://yihui.r-universe.dev")

I have had a long-standing interest in Rust and the interface between Rust and R, but for years it stayed on the “someday” list. This GitHub issue gave me a good excuse to finally try it out, so tinyimg is my first real experiment with the combination. I am happy with how it turned out. The goal is not just PNG—JPEG support is on the roadmap, and the name “tinyimg” rather than “tinypng” was deliberate.

Donate

As a freelancer (currently working as a contractor) and a dad of three kids, I truly appreciate your donation to support my writing and open-source software development! Your contribution helps me cope with financial uncertainty better, so I can spend more time on producing high-quality content and software. You can make a donation through methods below.

  • Venmo: @yihui_xie, or Zelle: [email protected]

  • Paypal

    • If you have a Paypal account, you can follow the link https://paypal.me/YihuiXie or find me on Paypal via my email [email protected]. Please choose the payment type as “Family and Friends” (instead of “Goods and Services”) to avoid extra fees.

    • If you don’t have Paypal, you may donate through this link via your debit or credit card. Paypal will charge a fee on my side.

  • Other ways:

    WeChat Pay (微信支付:谢益辉) Alipay (支付宝:谢益辉)
    WeChat Pay QR code Alipay QR code

When sending money, please be sure to add a note “gift” or “donation” if possible, so it won’t be treated as my taxable income but a genuine gift. Needless to say, donation is completely voluntary and I appreciate any amount you can give.

Please feel free to email me if you prefer a different way to give. Thank you very much!

I’ll give back a significant portion of the donations to the open-source community and charities. For the record, I received about $30,000 in total (before tax) in 2024-25, and gave back about $15,000 (after tax).