How Image Tiny Beats TinyPNG: A Free Offline Image Compressor Built with Tauri
This article introduces Image Tiny, a free, client‑side image compression tool built with Tauri, Rust, and WebAssembly, compares its compression rates with TinyPNG, details its features, technical implementation, code snippets, and shares practical tips and pitfalls for developers.
Introduction
Front‑end developers often struggle with large UI assets; Image Tiny offers a local, free solution that compresses PNG, JPG, and GIF files without network dependence.
Features
Supports PNG, JPG, GIF formats
Compresses images larger than 5 MB
Runs entirely in the browser (no server)
Drag‑and‑drop file compression
Adjustable compression quality
Window‑always‑on‑top option
Save single image
One‑click zip packaging of all images
Compression Rate Comparison with TinyPNG
For PNG images, Image Tiny achieves compression rates comparable to TinyPNG. For JPG, setting the quality below 80 % can surpass TinyPNG. TinyPNG does not support GIF or images larger than 5 MB, while Image Tiny does.
Technical Implementation
Compression Core
The core uses C libraries libimagequant, libpng, libjpeg, and gifsicle, compiled to WebAssembly via the Emscripten SDK for browser execution.
Application Framework
The desktop app is built with Tauri , Rust , Vue 3.0 , and Vite . See the related article “Throw away Electron, embrace Tauri” for more details.
Code Highlights
Window‑always‑on‑top
import { window } from '@tauri-apps/api';
function handleWindowTop() {
let curWin = window.getCurrent();
if (datas.winTop === '窗口置顶') {
curWin.setAlwaysOnTop(true);
datas.winTop = '取消置顶';
} else {
curWin.setAlwaysOnTop(false);
datas.winTop = '窗口置顶';
}
}Menu and Shortcut
use tauri::{Menu, MenuItem, Submenu};
fn main() {
let submenu_main = Submenu::new(
"ImageTiny".to_string(),
Menu::new()
.add_native_item(MenuItem::Minimize)
.add_native_item(MenuItem::Hide)
.add_native_item(MenuItem::Separator)
.add_native_item(MenuItem::CloseWindow)
.add_native_item(MenuItem::Quit),
);
let menu = Menu::new().add_submenu(submenu_main);
tauri::Builder::default()
.menu(menu)
.run(tauri::generate_context!())
.expect("error while running tauri application");
}Drag‑and‑Drop Compression
function dragenterEvent(event) { event.stopPropagation(); event.preventDefault(); }
function dragoverEvent(event) { event.stopPropagation(); event.preventDefault(); }
function dragleaveEvent(event) { event.stopPropagation(); event.preventDefault(); }
function dropEvent(event) {
event.stopPropagation();
event.preventDefault();
const files = event.dataTransfer.files;
displayChsFile(files);
}Single Image Save
import { writeBinaryFile, path, dialog } from '@tauri-apps/api';
async function handleSaveFile(file) {
const basePath = await path.downloadDir();
let selPath = await dialog.save({ defaultPath: basePath });
selPath = selPath.replace(/Untitled$/, '');
const reader = new FileReader();
reader.readAsArrayBuffer(file.data);
reader.onload = function(e) {
const fileU8A = new Uint8Array(e.target.result);
writeBinaryFile({ contents: fileU8A, path: `${selPath}${file.data.name}` });
};
}One‑Click Zip Save
import JSZip from 'jszip';
async function handleDownloadAll() {
const len = datas.imgList.length;
if (len === 0) return;
const zip = new JSZip();
for (let i = 0; i < len; i++) {
zip.file(datas.imgList[i].name, datas.imgList[i].data);
}
const basePath = await path.downloadDir();
let selPath = await dialog.save({ defaultPath: basePath });
selPath = selPath.replace(/Untitled$/, '');
const content = await zip.generateAsync({ type: 'blob' });
const reader = new FileReader();
reader.readAsArrayBuffer(content);
reader.onload = function(e) {
const fileU8A = new Uint8Array(e.target.result);
writeBinaryFile({ contents: fileU8A, path: `${selPath}IMG_${new Date().toISOString()}.zip` });
};
}Pitfalls
Tauri Version Selection
Newer Tauri versions restrict file‑system APIs to specific system paths, causing “cannot traverse directory” errors. The solution is to downgrade to older beta versions where the APIs are unrestricted.
File Upload Methods
Three approaches were evaluated: (1) native input upload (blocked by Tauri), (2) Tauri’s global file‑drop event (requires extra path handling), and (3) standard HTML drag‑and‑drop, which directly provides a FileList and was chosen for the app.
Why Window‑Always‑On‑Top?
Because Image Tiny relies on drag‑and‑drop, keeping the window on top prevents it from being hidden by other applications, improving user experience.
Installation Package
Source code: https://github.com/mxismean/image-tiny-tauri
Release binaries: https://github.com/mxismean/image-tiny-tauri/releases
Conclusion
The project took about a week to develop, involving many pitfalls, but the final tool provides a convenient, offline image compression experience for developers, and the author plans to create more useful Tauri‑based utilities.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
BaiPing Technology
Official account of the BaiPing app technology team. Dedicated to enhancing human productivity through technology. | DRINK FOR FUN!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
