├── serve.sh ├── docs ├── favicon.ico ├── dist │ ├── image_compressor.wasm │ └── image_compressor.js ├── wa.svg ├── style.css ├── worker.js ├── index.html └── index.js ├── .gitignore ├── clean.sh ├── .gitmodules ├── .github └── FUNDING.yml ├── README.md ├── CMakeLists.txt ├── src └── main.cpp └── LICENSE /serve.sh: -------------------------------------------------------------------------------- 1 | http-server ./docs -c-1 -p8080 -s 2 | -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antelle/wasm-image-compressor/HEAD/docs/favicon.ico -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .tmp 4 | *.log 5 | *.cmake 6 | CMakeCache.txt 7 | CMakeFiles 8 | Makefile 9 | -------------------------------------------------------------------------------- /docs/dist/image_compressor.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antelle/wasm-image-compressor/HEAD/docs/dist/image_compressor.wasm -------------------------------------------------------------------------------- /clean.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | rm -rf CMakeFiles CMakeCache.txt Makefile cmake_install.cmake dist Makefile *.cbp *ninja* .ninja* .tmp docs/dist 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/imagequant"] 2 | path = lib/imagequant 3 | url = git@github.com:ImageOptim/libimagequant.git 4 | ignore = dirty 5 | [submodule "lib/libpng"] 6 | path = lib/libpng 7 | url = git@github.com:glennrp/libpng.git 8 | ignore = dirty 9 | [submodule "lib/zlib"] 10 | path = lib/zlib 11 | url = git@github.com:madler/zlib.git 12 | ignore = dirty 13 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: antelle 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /docs/wa.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WASM Image compressor 2 | 3 | This is a PNG image compressor working in browser, built with WebAssembly. 4 | 5 | ## Libraries used 6 | 7 | - [libimagequant](https://pngquant.org/lib/) 8 | - [libpng](http://www.libpng.org/pub/png/libpng.html) 9 | - [zlib](http://www.zlib.net) 10 | 11 | ## Building 12 | 13 | Prerequisties: 14 | - Git 15 | - Emscripten 16 | - WebAssembly Toolchain 17 | - CMake 18 | 19 | ```bash 20 | git clone --recursive git@github.com:antelle/wasm-image-compressor.git 21 | cd wasm-image-compressor 22 | ./build.sh 23 | ``` 24 | 25 | ## Why? 26 | 27 | Nothing special here, it's just a demo of using some libs like zlib and libpng in WebAssembly. 28 | 29 | There's no particular reason to create a tool like this, however feel free to use it if you like it. It's just a simple Web UI for libimagequant. 30 | 31 | ## License 32 | 33 | [GPLv3](LICENSE) 34 | -------------------------------------------------------------------------------- /docs/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | margin: 0; 4 | padding: 0; 5 | /* https://color.adobe.com/Ive-got-friends-in-MERLOT-places-color-theme-10428977/?showPublished=true */ 6 | } 7 | body { 8 | font-family: -apple-system, "BlinkMacSystemFont", "Helvetica Neue", "Helvetica", "Arial", sans-serif; 9 | background: #FCFEF9; 10 | color: #1B1C21; 11 | padding: 10px; 12 | display: flex; 13 | flex-direction: column; 14 | align-items: stretch; 15 | justify-content: center; 16 | min-height: 100vh; 17 | } 18 | .wa-logo { 19 | width: 32px; 20 | float: right; 21 | } 22 | .drag-error { 23 | margin-top: 10px; 24 | display: none; 25 | color: #362F3E; 26 | } 27 | .drag-target { 28 | border: 1px dashed #EAC9C0; 29 | border-radius: 10px; 30 | color: #EAC9C0; 31 | padding: 20px; 32 | margin: 20px 0; 33 | flex-grow: 1; 34 | display: flex; 35 | flex-direction: column; 36 | align-items: center; 37 | justify-content: center; 38 | cursor: default; 39 | user-select: none; 40 | -webkit-user-select: none; 41 | -moz-user-select: none; 42 | } 43 | .drag-target.dragged { 44 | border-style: solid; 45 | background: #EAC9C0; 46 | color: #FCFEF9; 47 | } 48 | .input-file { 49 | position: absolute; 50 | left: -100000px; 51 | top: -100000px; 52 | visibility: hidden; 53 | } 54 | .work { 55 | display: none; 56 | flex-direction: column; 57 | } 58 | .images img { 59 | width: 50%; 60 | float: left; 61 | } 62 | .log { 63 | margin: 10px 0; 64 | } 65 | .link-download { 66 | float: right; 67 | display: none; 68 | } 69 | .footer, .footer a, .footer a:visited { 70 | margin-top: 6px; 71 | font-size: 10px; 72 | text-align: right; 73 | color: #EAC9C0; 74 | } 75 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.7) 2 | 3 | include($ENV{EMSCRIPTEN}/cmake/Modules/Platform/Emscripten.cmake) 4 | 5 | project(image_compressor) 6 | 7 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY .tmp/lib) 8 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY .tmp/lib) 9 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY docs/dist) 10 | set(CMAKE_EXECUTABLE_OUTPUT_PATH docs/dist) 11 | 12 | set(CMAKE_VERBOSE_MAKEFILE OFF) 13 | set(CMAKE_BUILD_TYPE MinSizeRel) 14 | set(CMAKE_C_FLAGS "-O3") 15 | set(CMAKE_CXX_FLAGS "-O3 --std=c++1z") 16 | set(CMAKE_EXE_LINKER_FLAGS "-O3 -g0 --memory-init-file 0 \ 17 | -s NO_FILESYSTEM=1 -s DEMANGLE_SUPPORT=0 -s ASSERTIONS=0 -s NO_EXIT_RUNTIME=1 \ 18 | -s ALLOW_MEMORY_GROWTH=1 \ 19 | -s WASM=1 -s DISABLE_EXCEPTION_CATCHING=1 \ 20 | -s EXPORTED_FUNCTIONS='[\"_compress\"]' \ 21 | -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'") 22 | 23 | include_directories(lib/imagequant) 24 | include_directories(lib/libpng) 25 | include_directories(lib/zlib) 26 | include_directories($ENV{EMSCRIPTEN}/system/include) 27 | 28 | set(OUTPUT_NAME hello) 29 | 30 | file(GLOB image_compressor_src "src/*.cpp") 31 | set(sources ${image_compressor_src}) 32 | 33 | add_executable(${PROJECT_NAME} ${sources}) 34 | 35 | file(GLOB imagequant_src 36 | "lib/imagequant/blur.c" 37 | "lib/imagequant/kmeans.c" 38 | "lib/imagequant/libimagequant.c" 39 | "lib/imagequant/mediancut.c" 40 | "lib/imagequant/mempool.c" 41 | "lib/imagequant/nearest.c" 42 | "lib/imagequant/pam.c" 43 | ) 44 | add_library(imagequant ${imagequant_src}) 45 | add_dependencies(${PROJECT_NAME} imagequant) 46 | 47 | add_subdirectory(lib/zlib) 48 | add_dependencies(${PROJECT_NAME} zlibstatic) 49 | 50 | set(ZLIB_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/lib/zlib") 51 | set(ZLIB_LIBRARY "${CMAKE_SOURCE_DIR}/lib/zlib/.tmp/lib/libz.a") 52 | set(M_LIBRARY "") 53 | 54 | add_subdirectory(lib/libpng) 55 | add_dependencies(${PROJECT_NAME} png_static) 56 | add_dependencies(png zlibstatic) 57 | 58 | target_link_libraries(${PROJECT_NAME} 59 | imagequant 60 | zlibstatic 61 | png 62 | ) 63 | -------------------------------------------------------------------------------- /docs/worker.js: -------------------------------------------------------------------------------- 1 | self.Module = { 2 | wasmBinaryFile: 'dist/image_compressor.wasm', 3 | print: log, 4 | printErr: logError 5 | }; 6 | 7 | importScripts('dist/image_compressor.js'); 8 | 9 | self.onmessage = e => { 10 | switch (e.data.type) { 11 | case 'image': 12 | processImage(e.data); 13 | break; 14 | } 15 | }; 16 | 17 | function processImage(args) { 18 | const { rgbData, width, height, fileSize, options } = args; 19 | try { 20 | log(`Working...`); 21 | const buffer = Module._malloc(rgbData.byteLength); 22 | Module.HEAPU8.set(rgbData, buffer); 23 | if (rgbData.byteLength !== width * height * 4) { 24 | self.postMessage({ type: 'error', error: `Invalid data length: ${rgbData.byteLength}, expected ${width * height * 4}` }); 25 | return; 26 | } 27 | const compressedSizePointer = Module._malloc(4); 28 | const { maxColors, dithering } = options; 29 | const result = Module._compress(width, height, maxColors, dithering, buffer, compressedSizePointer); 30 | if (result) { 31 | self.postMessage({ type: 'error', error: `Compression error: ${result}` }); 32 | } else { 33 | const compressedSize = Module.getValue(compressedSizePointer, 'i32', false); 34 | const percentage = (compressedSize / fileSize * 100).toFixed(1); 35 | log(`Compressed: ${fileSize} -> ${compressedSize} bytes (${percentage}%)`); 36 | const compressed = new Uint8Array(compressedSize); 37 | compressed.set(Module.HEAPU8.subarray(buffer, buffer + compressedSize)); 38 | self.postMessage({ type: 'result', result: compressed }); 39 | } 40 | Module._free(buffer); 41 | Module._free(compressedSizePointer); 42 | } catch (e) { 43 | logError(e.toString()); 44 | } 45 | } 46 | 47 | function log(msg) { 48 | self.postMessage({ type: 'log', msg }); 49 | } 50 | 51 | function logError(msg) { 52 | self.postMessage({ type: 'logError', msg }); 53 | } 54 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |Drag your PNG image or click here to open.
13 | 14 |