Skip to content

Commit

Permalink
feat(image): implement png_quantize
Browse files Browse the repository at this point in the history
  • Loading branch information
Brooooooklyn committed Jan 23, 2022
1 parent d0de72e commit 66f5e0f
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 3 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ Cargo.lock
!.yarn/versions
.turbo
*.tsbuildinfo
optimized-lossless.*
quantized.png
8 changes: 6 additions & 2 deletions optimize-test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
const { readFileSync, writeFileSync } = require('fs')

const { losslessCompressPng, compressJpeg } = require('./packages/binding')
const { losslessCompressPng, compressJpeg, pngQuantize } = require('./packages/binding')

writeFileSync('optimized-lossless.png', losslessCompressPng(readFileSync('./un-optimized.png')))
const PNG = readFileSync('./un-optimized.png')

writeFileSync('optimized-lossless.png', losslessCompressPng(PNG))

writeFileSync('quantized.png', pngQuantize(PNG))

writeFileSync('optimized-lossless.jpg', compressJpeg(readFileSync('./un-optimized.jpg')))
2 changes: 2 additions & 0 deletions packages/binding/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ oxipng_libdeflater = ["oxipng/libdeflater", "oxipng/parallel"]
with_simd = ["mozjpeg-sys/nasm_simd_parallel_build"]

[dependencies]
imagequant = "4.0.0-beta.8"
libc = "0.2"
lodepng = "3"
napi = {version = "2", default-features = false, features = ["napi3"]}
napi-derive = {version = "2", default-features = false, features = ["type-def"]}

Expand Down
7 changes: 7 additions & 0 deletions packages/binding/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,10 @@ export interface JpegCompressOptions {
optimizeScans?: boolean | undefined | null
}
export function compressJpeg(input: Buffer, options?: JpegCompressOptions | undefined | null): Buffer
export interface PngQuantOptions {
minQuality?: number | undefined | null
maxQuality?: number | undefined | null
speed?: number | undefined | null
posterization?: number | undefined | null
}
export function pngQuantize(input: Buffer, options?: PngQuantOptions | undefined | null): Buffer
3 changes: 2 additions & 1 deletion packages/binding/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,8 @@ if (!nativeBinding) {
throw new Error(`Failed to load native binding`)
}

const { losslessCompressPng, compressJpeg } = nativeBinding
const { losslessCompressPng, compressJpeg, pngQuantize } = nativeBinding

module.exports.losslessCompressPng = losslessCompressPng
module.exports.compressJpeg = compressJpeg
module.exports.pngQuantize = pngQuantize
77 changes: 77 additions & 0 deletions packages/binding/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,80 @@ extern "C" fn silence_message(
_level: std::os::raw::c_int,
) {
}

#[napi(object)]
#[derive(Default)]
pub struct PngQuantOptions {
// default is 70
pub min_quality: Option<u32>,
// default is 99
pub max_quality: Option<u32>,
// 1- 10
// Faster speeds generate images of lower quality, but may be useful for real-time generation of images.
// default: 5
pub speed: Option<u32>,
// Number of least significant bits to ignore.
// Useful for generating palettes for VGA, 15-bit textures, or other retro platforms.
pub posterization: Option<u32>,
}

#[napi]
pub fn png_quantize(input: Buffer, options: Option<PngQuantOptions>) -> Result<Buffer> {
let bitmap = lodepng::decode32(input.as_ref()).map_err(|err| {
Error::new(
Status::InvalidArg,
format!("Decode png from buffer failed{}", err),
)
})?;
let options = options.unwrap_or_default();
let width = bitmap.width;
let height = bitmap.height;
let mut liq = imagequant::new();
liq
.set_speed(options.speed.unwrap_or(5) as i32)
.map_err(|err| Error::new(Status::GenericFailure, format!("{}", err)))?;
liq
.set_quality(
options.min_quality.unwrap_or(70) as u8,
options.max_quality.unwrap_or(99) as u8,
)
.map_err(|err| Error::new(Status::GenericFailure, format!("{}", err)))?;
let mut img = liq
.new_image(
bitmap.buffer.as_slice(),
width as usize,
height as usize,
0.0,
)
.map_err(|err| {
Error::new(
Status::GenericFailure,
format!("Create image failed {}", err),
)
})?;
let mut quantization_result = liq
.quantize(&mut img)
.map_err(|err| Error::new(Status::GenericFailure, format!("quantize failed {}", err)))?;
quantization_result
.set_dithering_level(1.0)
.map_err(|err| Error::new(Status::GenericFailure, format!("{}", err)))?;
let (palette, pixels) = quantization_result
.remapped(&mut img)
.map_err(|err| Error::new(Status::GenericFailure, format!("remap failed {}", err)))?;
let mut encoder = lodepng::Encoder::new();
encoder.set_palette(palette.as_slice()).map_err(|err| {
Error::new(
Status::GenericFailure,
format!("Set palette on png encoder {}", err),
)
})?;
let output = encoder
.encode(pixels.as_slice(), width, height)
.map_err(|err| {
Error::new(
Status::GenericFailure,
format!("Encode quantized png failed {}", err),
)
})?;
Ok(output.into())
}

0 comments on commit 66f5e0f

Please sign in to comment.