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.

BaiPing Technology
BaiPing Technology
BaiPing Technology
How Image Tiny Beats TinyPNG: A Free Offline Image Compressor Built with Tauri

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

RustTauriimage compressionfrontend tool
BaiPing Technology
Written by

BaiPing Technology

Official account of the BaiPing app technology team. Dedicated to enhancing human productivity through technology. | DRINK FOR FUN!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.