Skip to content

Commit

Permalink
release: 2.4.4
Browse files Browse the repository at this point in the history
  • Loading branch information
joshstoik1 committed Jan 8, 2024
2 parents 22b2a1c + 40b178a commit 2854bcf
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 74 deletions.
6 changes: 3 additions & 3 deletions CREDITS.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Project Dependencies
Package: flaca
Version: 2.4.3
Generated: 2024-01-06 06:34:27 UTC
Version: 2.4.4
Generated: 2024-01-08 04:24:33 UTC

| Package | Version | Author(s) | License |
| ---- | ---- | ---- | ---- |
Expand All @@ -24,7 +24,7 @@
| [fyi_msg](https://github.com/Blobfolio/fyi) | 0.11.8 | [Blobfolio, LLC.](mailto:hello@blobfolio.com) | WTFPL |
| [hashbrown](https://github.com/rust-lang/hashbrown) | 0.14.3 | [Amanieu d'Antras](mailto:amanieu@gmail.com) | Apache-2.0 or MIT |
| [indexmap](https://github.com/bluss/indexmap) | 2.1.0 | | Apache-2.0 or MIT |
| [libc](https://github.com/rust-lang/libc) | 0.2.151 | The Rust Project Developers | Apache-2.0 or MIT |
| [libc](https://github.com/rust-lang/libc) | 0.2.152 | The Rust Project Developers | Apache-2.0 or MIT |
| [libdeflate-sys](https://github.com/adamkewley/libdeflater) | 1.19.0 | [Adam Kewley](mailto:contact@adamkewley.com) | Apache-2.0 |
| [libdeflater](https://github.com/adamkewley/libdeflater) | 1.19.0 | [Adam Kewley](mailto:contact@adamkewley.com) | Apache-2.0 |
| [log](https://github.com/rust-lang/log) | 0.4.20 | The Rust Project Developers | Apache-2.0 or MIT |
Expand Down
8 changes: 2 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "flaca"
version = "2.4.3"
version = "2.4.4"
license = "WTFPL"
authors = ["Josh Stoik <josh@blobfolio.com>"]
edition = "2021"
Expand All @@ -18,7 +18,7 @@ exclude = [

[package.metadata.deb]
maintainer = "Josh Stoik <hello@blobfolio.com>"
copyright = "2022, Blobfolio, LLC <hello@blobfolio.com>"
copyright = "2024, Blobfolio, LLC <hello@blobfolio.com>"
license-file = ["./LICENSE", "0"]
extended-description = """\n\
Flaca is a CLI tool for x86-64 Linux machines that simplifies the task of losslessly compressing JPEG and PNG images for use on the web.\n\
Expand Down Expand Up @@ -125,11 +125,7 @@ default-features = false
cc = "1.0.*"
dowser = "0.8.*"

[profile.release.package."*"]
opt-level = 3

[profile.release]
lto = true
codegen-units = 1
opt-level = 1
strip = true
4 changes: 2 additions & 2 deletions release/man/flaca.1
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.TH "FLACA" "1" "January 2024" "Flaca v2.4.3" "User Commands"
.TH "FLACA" "1" "January 2024" "Flaca v2.4.4" "User Commands"
.SH NAME
Flaca \- Manual page for flaca v2.4.3.
Flaca \- Manual page for flaca v2.4.4.
.SH DESCRIPTION
Brute\-force, lossless JPEG and PNG compression.
.SS USAGE:
Expand Down
218 changes: 156 additions & 62 deletions src/image/jpegtran.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use mozjpeg_sys::{
j_compress_ptr,
j_decompress_ptr,
JCROP_CODE_JCROP_UNSET,
jpeg_common_struct,
jpeg_compress_struct,
jpeg_copy_critical_parameters,
jpeg_create_compress,
Expand All @@ -43,16 +44,20 @@ use mozjpeg_sys::{
jvirt_barray_ptr,
JXFORM_CODE_JXFORM_NONE,
};
use std::ffi::{
c_uint,
c_ulong,
use std::{
ffi::{
c_int,
c_uint,
c_ulong,
},
marker::PhantomPinned,
};
use super::ffi::EncodedImage;



// We need a couple more things from jpegtran. Mozjpeg-sys includes the right
// sources but doesn't export the definitions.
// sources but doesn't export these definitions for whatever reason.
extern "C-unwind" {
fn jcopy_markers_setup(srcinfo: j_decompress_ptr, option: c_uint);
fn jcopy_markers_execute(
Expand Down Expand Up @@ -101,139 +106,228 @@ pub(super) fn optimize(src: &[u8]) -> Option<EncodedImage<c_ulong>> {
iMCU_sample_height: 0,
};

let mut meta = InOut::default();
// Our original image length.
let src_size = src.len() as c_ulong; // We know this fits.

// Set up the decompression/compression structs.
let mut srcinfo = JpegSrcInfo::from(src);
let mut dstinfo = JpegDstInfo::from(&mut srcinfo);

unsafe {
// Load the source file.
jpeg_mem_src(&mut meta.src, src.as_ptr(), src_size);
jpeg_mem_src(&mut srcinfo.cinfo, srcinfo.raw.as_ptr(), src_size);

// Ignore markers.
jcopy_markers_setup(&mut meta.src, 0);
jcopy_markers_setup(&mut srcinfo.cinfo, 0);

// Read the file header to get to the goods.
jpeg_read_header(&mut meta.src, 1);
jpeg_read_header(&mut srcinfo.cinfo, 1);

// Read a few more properties into the source struct.
if jtransform_request_workspace(&mut meta.src, &mut transformoption) == 0 {
if jtransform_request_workspace(&mut srcinfo.cinfo, &mut transformoption) == 0 {
return None;
}
}

// Read source file as DCT coefficients.
let src_coef_arrays: *mut jvirt_barray_ptr = unsafe {
jpeg_read_coefficients(&mut meta.src)
jpeg_read_coefficients(&mut srcinfo.cinfo)
};

// Initialize destination compression parameters from source values.
unsafe { jpeg_copy_critical_parameters(&meta.src, &mut meta.dst); }
unsafe { jpeg_copy_critical_parameters(&srcinfo.cinfo, &mut dstinfo.cinfo); }

// Adjust destination parameters if required by transform options, and sync
// the coefficient arrays.
let dst_coef_arrays: *mut jvirt_barray_ptr = unsafe {
jtransform_adjust_parameters(
&mut meta.src,
&mut meta.dst,
&mut srcinfo.cinfo,
&mut dstinfo.cinfo,
src_coef_arrays,
&mut transformoption,
)
};

// Turn on "code optimizing".
meta.dst.optimize_coding = 1;
dstinfo.cinfo.optimize_coding = 1;

// Compress!
let mut out = EncodedImage::default();
unsafe {
// Enable "progressive".
jpeg_simple_progression(&mut meta.dst);
jpeg_simple_progression(&mut dstinfo.cinfo);

// And load the destination file.
jpeg_mem_dest(&mut meta.dst, &mut out.buf, &mut out.size);
jpeg_mem_dest(&mut dstinfo.cinfo, &mut out.buf, &mut out.size);

// Start the compressor. Note: no data is written here.
jpeg_write_coefficients(&mut meta.dst, dst_coef_arrays);
jpeg_write_coefficients(&mut dstinfo.cinfo, dst_coef_arrays);

// Make sure we aren't copying any markers.
jcopy_markers_execute(&mut meta.src, &mut meta.dst, 0);
jcopy_markers_execute(&mut srcinfo.cinfo, &mut dstinfo.cinfo, 0);

// Execute and write the transformation, if any.
jtransform_execute_transform(
&mut meta.src,
&mut meta.dst,
&mut srcinfo.cinfo,
&mut dstinfo.cinfo,
src_coef_arrays,
&mut transformoption,
);
}

// Finish it up, and note whether or not it (probably) worked.
let happy = dstinfo.finish();

// The decompression will have finished much earlier, but we had to wait
// to call this deconstructor until now because of all the shared
// references.
unsafe { jpeg_finish_decompress(&mut srcinfo.cinfo); }

// Return it if we got it!
if meta.build() && ! out.is_empty() && out.size < src_size {
Some(out)
}
if happy && ! out.is_empty() && out.size < src_size { Some(out) }
else { None }
}



/// # Source and Destination Data.
/// # JPEG Source Info.
///
/// This wrapper struct exists to help ensure C memory is freed correctly on
/// exit.
struct InOut {
src_err: jpeg_error_mgr,
src: jpeg_decompress_struct,
dst_err: jpeg_error_mgr,
dst: jpeg_compress_struct,
built: bool,
/// This struct is used to parse the source image details and related errors.
/// The abstraction is primarily used to ensure the C-related resources are
/// correctly broken down on drop.
struct JpegSrcInfo<'a> {
raw: &'a [u8],
cinfo: jpeg_decompress_struct,
err: Box<jpeg_error_mgr>,
}

impl Default for InOut {
impl<'a> From<&'a [u8]> for JpegSrcInfo<'a> {
#[allow(unsafe_code)]
fn default() -> Self {
fn from(raw: &'a [u8]) -> Self {
let mut out = Self {
src_err: unsafe { std::mem::zeroed() },
src: unsafe { std::mem::zeroed() },
dst_err: unsafe { std::mem::zeroed() },
dst: unsafe { std::mem::zeroed() },
built: false,
raw,
cinfo: unsafe { std::mem::zeroed() },
err: new_err(),
};

// Initialize the memory.
unsafe {
out.src.common.err = jpeg_std_error(&mut out.src_err);
out.dst.common.err = jpeg_std_error(&mut out.dst_err);
jpeg_create_decompress(&mut out.src);
jpeg_create_compress(&mut out.dst);
// Set up the error, then the struct.
out.cinfo.common.err = std::ptr::addr_of_mut!(*out.err);
jpeg_create_decompress(&mut out.cinfo);
}

// The trace levels should already match, but just in case…
out.src_err.trace_level = out.dst_err.trace_level;

// Done!
out
}
}

impl InOut {
impl<'a> Drop for JpegSrcInfo<'a> {
#[allow(unsafe_code)]
/// # Finish Compression.
fn build(&mut self) -> bool {
// Only build once.
if self.built { false }
else {
unsafe { jpeg_finish_compress(&mut self.dst) };
self.built = true;
0 == unsafe { (*self.dst.common.err).msg_code }
fn drop(&mut self) {
unsafe { jpeg_destroy_decompress(&mut self.cinfo); }
}
}



/// # JPEG Destination Info.
///
/// This struct is used to hold the output-related image details, but not the
/// image itself.
///
/// On the surface, this looks almost exactly like the `JpegSrcInfo` wrapper,
/// but its error is a raw pointer because `mozjpeg` is really weird. Haha.
struct JpegDstInfo {
cinfo: jpeg_compress_struct,
err: *mut jpeg_error_mgr,
_pin: PhantomPinned,
}

impl From<&mut JpegSrcInfo<'_>> for JpegDstInfo {
#[allow(unsafe_code)]
fn from(src: &mut JpegSrcInfo<'_>) -> Self {
let mut out = Self {
cinfo: unsafe { std::mem::zeroed() },
err: Box::into_raw(new_err()),
_pin: PhantomPinned,
};

unsafe {
// Set up the error, then the struct.
out.cinfo.common.err = std::ptr::addr_of_mut!(*out.err);
jpeg_create_compress(&mut out.cinfo);

// Sync the source trace level with the destination.
src.err.trace_level = (*out.err).trace_level;
}

out
}
}

impl Drop for InOut {
impl Drop for JpegDstInfo {
#[allow(unsafe_code)]
fn drop(&mut self) {
self.build();
unsafe {
jpeg_destroy_compress(&mut self.dst);
jpeg_finish_decompress(&mut self.src);
jpeg_destroy_decompress(&mut self.src);
jpeg_destroy_compress(&mut self.cinfo);

// The error pointer is no longer accessible.
let _ = Box::from_raw(self.err);
}
}
}

impl JpegDstInfo {
#[allow(unsafe_code)]
/// # Finish Compression!
///
/// This finishes writing the new image, consuming the details struct in
/// the process.
///
/// A simple `true`/`false` boolean is returned to indicate (likely)
/// success.
fn finish(mut self) -> bool {
unsafe {
jpeg_finish_compress(&mut self.cinfo);
0 == (*self.cinfo.common.err).msg_code
}
}
}



#[allow(clippy::unnecessary_box_returns, unsafe_code)]
/// # New Unwinding Error.
///
/// Mozjpeg is largely designed to panic anytime there's an error instead of
/// returning helpful status messages or anything like that.
///
/// This initializes a new error struct for de/compression use with handlers
/// set to suppress the messaging and unwind.
///
/// Shout out to the [mozjpeg](https://github.com/ImageOptim/mozjpeg-rust/blob/main/src/errormgr.rs)
/// crate for the inspiration!
fn new_err() -> Box<jpeg_error_mgr> {
unsafe {
let mut err = Box::new(std::mem::zeroed());
jpeg_std_error(&mut err);
err.error_exit = Some(unwind_error_exit);
err.emit_message = Some(silence_message);
err
}
}

#[cold]
/// # Error Message.
///
/// This is a noop method; no error message is printed.
extern "C-unwind" fn silence_message(_cinfo: &mut jpeg_common_struct, _msg_level: c_int) {}

#[cold]
#[allow(unsafe_code)]
/// # Error Exit.
///
/// Emit an unwinding panic so we can recover somewhat gracefully from mozjpeg
/// errors.
extern "C-unwind" fn unwind_error_exit(_cinfo: &mut jpeg_common_struct) {
std::panic::resume_unwind(Box::new(()));
}
12 changes: 11 additions & 1 deletion src/image/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,17 @@ pub(super) fn encode(file: &Path, kinds: ImageKind, oxi: &OxipngOptions) -> Opti
// Do JPEG stuff?
else if ImageKind::is_jpeg(&raw) {
if ImageKind::None == kinds & ImageKind::Jpeg { return None; }
encode_mozjpeg(&mut raw);

// Mozjpeg usually panics on error, so we have to do a weird little
// dance to keep it from killing the whole thread.
raw = std::panic::catch_unwind(move || {
encode_mozjpeg(&mut raw);
raw
}).ok()?;

// Double-check the image type again, just in case the copy itself
// panicked.
if ! ImageKind::is_jpeg(&raw) { return None; }
}
// Bad image.
else { return None; }
Expand Down

0 comments on commit 2854bcf

Please sign in to comment.