Skip to content

Commit

Permalink
release: 0.4.1
Browse files Browse the repository at this point in the history
  • Loading branch information
joshstoik1 committed May 5, 2021
2 parents 5b28212 + 33b6503 commit b7aaac5
Show file tree
Hide file tree
Showing 14 changed files with 165 additions and 270 deletions.
4 changes: 2 additions & 2 deletions CREDITS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2553,8 +2553,8 @@ SOFTWARE.
* [dactyl 0.1.7](https://github.com/Blobfolio/dactyl)
* [dowser 0.2.2](https://github.com/Blobfolio/dowser)
* [fyi_msg 0.7.1](https://github.com/Blobfolio/fyi)
* [refract 0.4.0](https://github.com/Blobfolio/refract)
* [refract_core 0.4.0](https://github.com/Blobfolio/refract)
* [refract 0.4.1](https://github.com/Blobfolio/refract)
* [refract_core 0.4.1](https://github.com/Blobfolio/refract)


```
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ The latter is compensated for by automatically repeating the chosen "best" encod

Color sources are outputted using `Y′UV444`, while greyscale sources are outputted using `Y′UV400` instead.

Speaking of color sources, Refract will first attempt encoding using limited-range YCbCr as that usually reduces output sizes 2-5%. Because YCbCr can result in color shifting or other undesired distortion, if none of the candidates look good, it will rerun the process using full-range RGB.
Speaking of color sources, Refract attempts AVIF encoding using both limited-range YCbCr and full-range RGB methods. YCbCr typically results in slightly smaller output but may lead to more noticeable color shifts. If you want to skip this, use the `--skip-ycbcr` flag.

Because YCbCr particularly messes with blacks and whites, greyscale images are only ever encoded in full-range mode.
Grescale sources are only ever attempted using full-range RGB.

**Note:**
>The upcoming release of Chrome v.91 is introducing stricter requirements for AVIF images that will [prevent the rendering of many previously valid sources](https://bugs.chromium.org/p/chromium/issues/detail?id=1115483). This will break a fuckton of images, including those created with Refract < `0.3.1`. Be sure to regenerate any such images using `0.3.1+` to avoid any sadness.
Expand All @@ -84,6 +84,7 @@ The following flags are available:
--no-avif Skip AVIF conversion.
--no-jxl Skip JPEG XL conversion.
--no-webp Skip WebP conversion.
--skip-ycbcr Only test full-range RGB AVIF encoding (when encoding AVIFs).
-V, --version Prints version information.
```

Expand Down
6 changes: 5 additions & 1 deletion refract/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "refract"
version = "0.4.0"
version = "0.4.1"
license = "WTFPL"
authors = ["Josh Stoik <[email protected]>"]
edition = "2018"
Expand Down Expand Up @@ -48,6 +48,10 @@ description = "Skip JPEG XL conversion."
long = "--no-webp"
description = "Skip WebP conversion."

[[package.metadata.bashman.switches]]
long = "--skip-ycbcr"
description = "Only test full-range RGB AVIF encoding (when encoding AVIFs)."

[[package.metadata.bashman.switches]]
short = "-V"
long = "--version"
Expand Down
57 changes: 8 additions & 49 deletions refract/src/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ use dactyl::{
};
use fyi_msg::Msg;
use refract_core::{
FLAG_AVIF_LIMITED,
Output,
OutputKind,
RefractError,
Expand Down Expand Up @@ -52,7 +51,7 @@ impl<'a> Drop for ImageCli<'a> {

impl<'a> ImageCli<'a> {
/// # New Instance.
pub(crate) fn new(src: &'a Source, kind: OutputKind) -> Self {
pub(crate) fn new(src: &'a Source, kind: OutputKind, flags: u8) -> Self {
// Let's start by setting up the file system paths we'll be using for
// preview and permanent output.
let stub: &[u8] = src.path().as_os_str().as_bytes();
Expand All @@ -65,14 +64,6 @@ impl<'a> ImageCli<'a> {
let _res = std::fs::File::create(&tmp);
}

// Default limited mode for AVIF when appropriate. If no candidate is
// chosen, the process will repeat in full RGB mode.
let flags: u8 =
if kind == OutputKind::Avif && src.supports_yuv_limited() {
FLAG_AVIF_LIMITED
}
else { 0 };

Self {
src,
kind,
Expand All @@ -83,17 +74,9 @@ impl<'a> ImageCli<'a> {
}

/// # Encode.
pub(crate) fn encode(mut self) {
pub(crate) fn encode(self) {
// Print a header for the encoding type.
println!(
"\x1b[34m[\x1b[96;1m{}\x1b[0;34m]\x1b[0m{}",
self.kind,
// Append a subtitle for limited-range AVIF.
if FLAG_AVIF_LIMITED == self.flags & FLAG_AVIF_LIMITED {
" \x1b[2m(YCbCr)\x1b[0m"
}
else { "" }
);
println!("\x1b[34m[\x1b[96;1m{}\x1b[0;34m]\x1b[0m", self.kind);

// We'll be re-using this prompt throughout.
let prompt = Msg::plain(format!(
Expand All @@ -119,7 +102,7 @@ impl<'a> ImageCli<'a> {

// Wrap it up!
let time = guide.time();
let res = self.finish(guide.take());
self.finish(guide.take());

// Print the timings.
Msg::plain(format!(
Expand All @@ -128,33 +111,17 @@ impl<'a> ImageCli<'a> {
))
.with_indent(1)
.print();

// We might want to re-run AVIF in full mode. This only applies if no
// candidate was found using YCbCr.
if
! res &&
self.kind == OutputKind::Avif &&
FLAG_AVIF_LIMITED == self.flags & FLAG_AVIF_LIMITED
{
self.flags = 0;
self.encode()
}
}

/// # Finish.
fn finish(&self, result: Result<Output, RefractError>) -> bool {
fn finish(self, result: Result<Output, RefractError>) {
// Handle results.
match result {
Ok(result) => match save_image(&self.dst, &result) {
Ok(_) => print_success(self.src.size().get(), &result, &self.dst),
Err(e) => print_error(e),
},
Err(e) => {
if self.dst.exists() {
let _res = std::fs::remove_file(&self.dst);
}
print_error(e)
}
Err(e) => print_error(e),
}
}
}
Expand Down Expand Up @@ -183,20 +150,14 @@ pub(super) fn print_path_title(path: &Path) {
}

/// # Print Error.
///
/// This always returns false.
fn print_error(err: RefractError) -> bool {
fn print_error(err: RefractError) {
Msg::warning(err.as_str())
.with_indent(1)
.print();

false
}

/// # Print Success.
///
/// This always returns true.
fn print_success(src_size: u64, output: &Output, dst_path: &Path) -> bool {
fn print_success(src_size: u64, output: &Output, dst_path: &Path) {
let diff: u64 = src_size - output.size().get();
let per = dactyl::int_div_float(diff, src_size);
let name = dst_path.file_name()
Expand Down Expand Up @@ -224,8 +185,6 @@ fn print_success(src_size: u64, output: &Output, dst_path: &Path) -> bool {
}
)
.print();

true
}

/// # Write Result.
Expand Down
12 changes: 11 additions & 1 deletion refract/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ use argyle::{
};
use image::ImageCli;
use refract_core::{
FLAG_NO_AVIF_LIMITED,
OutputKind,
RefractError,
Source,
Expand Down Expand Up @@ -86,13 +87,20 @@ fn _main() -> Result<(), RefractError> {
.map_err(RefractError::Menu)?
.with_list();

// We'll get to these in a bit.
let mut flags: u8 = 0;

// Figure out which types we're dealing with.
let mut encoders: Vec<OutputKind> = Vec::with_capacity(2);
if ! args.switch(b"--no-webp") {
encoders.push(OutputKind::Webp);
}
if ! args.switch(b"--no-avif") {
encoders.push(OutputKind::Avif);

if args.switch(b"--skip-ycbcr") {
flags |= FLAG_NO_AVIF_LIMITED;
}
}
if ! args.switch(b"--no-jxl") {
encoders.push(OutputKind::Jxl);
Expand Down Expand Up @@ -124,7 +132,7 @@ fn _main() -> Result<(), RefractError> {

match Source::try_from(x) {
Ok(img) => encoders.iter()
.map(|&e| ImageCli::new(&img, e))
.map(|&e| ImageCli::new(&img, e, flags))
.for_each(ImageCli::encode),
Err(e) => Msg::error(e.as_str()).print(),
}
Expand Down Expand Up @@ -172,6 +180,8 @@ FLAGS:
--no-avif Skip AVIF conversion.
--no-jxl Skip JPEG XL conversion.
--no-webp Skip WebP conversion.
--skip-ycbcr Only test full-range RGB AVIF encoding (when encoding
AVIFs).
-V, --version Prints version information.
OPTIONS:
Expand Down
2 changes: 1 addition & 1 deletion refract_core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "refract_core"
version = "0.4.0"
version = "0.4.1"
license = "WTFPL"
authors = ["Josh Stoik <[email protected]>"]
edition = "2018"
Expand Down
49 changes: 32 additions & 17 deletions refract_core/src/enc/avif.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@

use crate::{
Candidate,
FLAG_AVIF_LIMITED,
FLAG_AVIF_SLOW,
Image,
RefractError,
};
use libavif_sys::{
AVIF_CHROMA_SAMPLE_POSITION_COLOCATED,
AVIF_CHROMA_UPSAMPLING_BILINEAR,
AVIF_CODEC_CHOICE_RAV1E,
AVIF_COLOR_PRIMARIES_BT709,
AVIF_MATRIX_COEFFICIENTS_BT709,
Expand All @@ -18,6 +21,7 @@ use libavif_sys::{
AVIF_RANGE_FULL,
AVIF_RANGE_LIMITED,
AVIF_RESULT_OK,
AVIF_RGB_FORMAT_RGBA,
AVIF_TRANSFER_CHARACTERISTICS_SRGB,
avifEncoder,
avifEncoderCreate,
Expand All @@ -26,7 +30,9 @@ use libavif_sys::{
avifImage,
avifImageCreate,
avifImageDestroy,
avifImageRGBToYUV,
avifResult,
avifRGBImage,
avifRWData,
avifRWDataFree,
};
Expand Down Expand Up @@ -101,20 +107,36 @@ impl Drop for LibAvifEncoder {
/// garbage cleanup.
struct LibAvifImage(*mut avifImage);

impl TryFrom<&Image<'_>> for LibAvifImage {
type Error = RefractError;

impl LibAvifImage {
#[allow(clippy::cast_possible_truncation)] // The values are purpose-made.
fn try_from(src: &Image) -> Result<Self, Self::Error> {
fn new(src: &Image, flags: u8) -> Result<Self, RefractError> {
// Make sure dimensions fit u32.
let width = src.width_u32()?;
let height = src.height_u32()?;

// AVIF dimensions can't exceed this amount. We might as well bail as
// early as possible.
if src.width() * src.height() > 16_384 * 16_384 {
return Err(RefractError::Overflow);
}

let limited = src.is_yuv_limited();
let limited = FLAG_AVIF_LIMITED == flags & FLAG_AVIF_LIMITED;
let greyscale: bool = src.color_kind().is_greyscale();

// Make an "avifRGBImage" from our buffer.
let raw: &[u8] = &*src;
let rgb = avifRGBImage {
width,
height,
depth: 8,
format: AVIF_RGB_FORMAT_RGBA,
chromaUpsampling: AVIF_CHROMA_UPSAMPLING_BILINEAR,
ignoreAlpha: ! src.color_kind().has_alpha() as _,
alphaPremultiplied: 0,
pixels: raw.as_ptr() as *mut u8,
rowBytes: 4 * width,
};

// And convert it to YUV.
let yuv = unsafe {
let tmp = avifImageCreate(
Expand All @@ -128,19 +150,10 @@ impl TryFrom<&Image<'_>> for LibAvifImage {
// This shouldn't happen, but could, maybe.
if tmp.is_null() { return Err(RefractError::Encode); }

let (yuv_planes, yuv_row_bytes, alpha_plane, alpha_row_bytes) = src.yuv();

(*tmp).imageOwnsYUVPlanes = 0;
(*tmp).yuvRange =
if limited { AVIF_RANGE_LIMITED }
else { AVIF_RANGE_FULL };
(*tmp).yuvPlanes = yuv_planes;
(*tmp).yuvRowBytes = yuv_row_bytes;

(*tmp).imageOwnsAlphaPlane = 0;
(*tmp).alphaRange = AVIF_RANGE_FULL;
(*tmp).alphaPlane = alpha_plane;
(*tmp).alphaRowBytes = alpha_row_bytes;

(*tmp).yuvChromaSamplePosition = AVIF_CHROMA_SAMPLE_POSITION_COLOCATED;
(*tmp).colorPrimaries = AVIF_COLOR_PRIMARIES_BT709 as _;
Expand All @@ -149,6 +162,8 @@ impl TryFrom<&Image<'_>> for LibAvifImage {
if greyscale || limited { AVIF_MATRIX_COEFFICIENTS_BT709 as _ }
else { AVIF_MATRIX_COEFFICIENTS_IDENTITY as _ };

maybe_die(avifImageRGBToYUV(tmp, &rgb))?;

tmp
};

Expand Down Expand Up @@ -287,13 +302,13 @@ pub(super) fn make_lossy(
img: &Image,
candidate: &mut Candidate,
quality: NonZeroU8,
tiling: bool
flags: u8
) -> Result<(), RefractError> {
let image = LibAvifImage::try_from(img)?;
let image = LibAvifImage::new(img, flags)?;
let encoder = LibAvifEncoder::try_from(quality)?;

// Configure tiling.
if tiling {
if 0 == FLAG_AVIF_SLOW & flags {
if let Some((x, y)) = tiles(img.width(), img.height()) {
unsafe {
(*encoder.0).tileRowsLog2 = x;
Expand Down
Loading

0 comments on commit b7aaac5

Please sign in to comment.