blob: 1273073f791f4ec052dca7c419ccde74e4b5b8cd [file]
// Copyright 2025 the Vello Authors
// SPDX-License-Identifier: Apache-2.0 OR MIT
//! Fine rasterization runs the commands in each wide tile to determine the final RGBA value
//! of each pixel and pack it into the pixmap.
mod blend;
mod gradient;
mod image;
mod rounded_blurred_rect;
use crate::fine::gradient::GradientFiller;
use crate::fine::image::ImageFiller;
use crate::fine::rounded_blurred_rect::BlurredRoundedRectFiller;
use crate::util::scalar::div_255;
use alloc::vec;
use alloc::vec::Vec;
use core::fmt::Debug;
use core::iter;
use std::ops::{Add, Div, Mul, Sub};
use vello_common::encode::{EncodedKind, EncodedPaint};
use vello_common::paint::{Paint, PremulColor};
use vello_common::peniko::{BlendMode, Compose, Mix};
use vello_common::{
coarse::{Cmd, WideTile},
tile::Tile,
};
pub(crate) const COLOR_COMPONENTS: usize = 4;
pub(crate) const TILE_HEIGHT_COMPONENTS: usize = Tile::HEIGHT as usize * COLOR_COMPONENTS;
#[doc(hidden)]
pub const SCRATCH_BUF_SIZE: usize =
WideTile::WIDTH as usize * Tile::HEIGHT as usize * COLOR_COMPONENTS;
pub type ScratchBuf<F> = [F; SCRATCH_BUF_SIZE];
pub type FineU8 = ScratchBuf<u8>;
pub type FineF32 = ScratchBuf<f32>;
#[derive(Debug)]
#[doc(hidden)]
/// This is an internal struct, do not access directly.
pub struct Fine<F: FineType> {
pub(crate) width: u16,
pub(crate) height: u16,
pub(crate) wide_coords: (u16, u16),
pub(crate) blend_buf: Vec<ScratchBuf<F>>,
pub(crate) color_buf: ScratchBuf<F>,
}
impl<F: FineType> Fine<F> {
/// Create a new fine rasterizer.
pub fn new(width: u16, height: u16) -> Self {
let blend_buf = [F::ZERO; SCRATCH_BUF_SIZE];
let color_buf = [F::ZERO; SCRATCH_BUF_SIZE];
Self {
width,
height,
wide_coords: (0, 0),
blend_buf: vec![blend_buf],
color_buf,
}
}
/// Set the coordinates of the current wide tile that is being processed (in tile units).
pub fn set_coords(&mut self, x: u16, y: u16) {
self.wide_coords = (x, y);
}
pub fn clear(&mut self, premul_color: [F; 4]) {
let blend_buf = self.blend_buf.last_mut().unwrap();
if premul_color[0] == premul_color[1]
&& premul_color[1] == premul_color[2]
&& premul_color[2] == premul_color[3]
{
// All components are the same, so we can use memset instead.
blend_buf.fill(premul_color[0]);
} else {
for z in blend_buf.chunks_exact_mut(COLOR_COMPONENTS) {
z.copy_from_slice(&premul_color);
}
}
}
#[doc(hidden)]
pub fn pack(&mut self, out_buf: &mut [u8]) {
let blend_buf = self.blend_buf.last_mut().unwrap();
pack(
out_buf,
blend_buf,
self.width.into(),
self.height.into(),
self.wide_coords.0.into(),
self.wide_coords.1.into(),
);
}
pub(crate) fn run_cmd(&mut self, cmd: &Cmd, alphas: &[u8], paints: &[EncodedPaint]) {
match cmd {
Cmd::Fill(f) => {
self.fill(
usize::from(f.x),
usize::from(f.width),
&f.paint,
f.blend_mode
.unwrap_or(BlendMode::new(Mix::Normal, Compose::SrcOver)),
paints,
);
}
Cmd::AlphaFill(s) => {
let a_slice = &alphas[s.alpha_idx..];
self.strip(
usize::from(s.x),
usize::from(s.width),
a_slice,
&s.paint,
s.blend_mode
.unwrap_or(BlendMode::new(Mix::Normal, Compose::SrcOver)),
paints,
);
}
Cmd::PushBuf => {
self.blend_buf.push([F::ZERO; SCRATCH_BUF_SIZE]);
}
Cmd::PopBuf => {
self.blend_buf.pop();
}
Cmd::ClipFill(cf) => {
self.clip_fill(cf.x as usize, cf.width as usize);
}
Cmd::ClipStrip(cs) => {
let aslice = &alphas[cs.alpha_idx..];
self.clip_strip(cs.x as usize, cs.width as usize, aslice);
}
Cmd::Blend(cb) => {
self.apply_blend(*cb);
}
Cmd::Opacity(o) => {
if *o != 1.0 {
self.blend_buf
.last_mut()
.unwrap()
.chunks_exact_mut(TILE_HEIGHT_COMPONENTS)
.for_each(|s| {
for c in s {
*c = F::from_normalized_f32(*o).normalized_mul(*c);
}
});
}
}
Cmd::Mask(m) => {
let start_x = self.wide_coords.0 * WideTile::WIDTH;
let start_y = self.wide_coords.1 * Tile::HEIGHT;
for (x, col) in self
.blend_buf
.last_mut()
.unwrap()
.chunks_exact_mut(TILE_HEIGHT_COMPONENTS)
.enumerate()
{
for (y, pix) in col.chunks_exact_mut(COLOR_COMPONENTS).enumerate() {
let x = start_x + x as u16;
let y = start_y + y as u16;
if x < m.width() && y < m.height() {
let val = F::from_normalized_u8(m.sample(x, y));
for comp in pix.iter_mut() {
*comp = comp.normalized_mul(val);
}
}
}
}
}
}
}
/// Fill at a given x and with a width using the given paint.
pub fn fill(
&mut self,
x: usize,
width: usize,
fill: &Paint,
blend_mode: BlendMode,
encoded_paints: &[EncodedPaint],
) {
let blend_buf = &mut self.blend_buf.last_mut().unwrap()[x * TILE_HEIGHT_COMPONENTS..]
[..TILE_HEIGHT_COMPONENTS * width];
let color_buf =
&mut self.color_buf[x * TILE_HEIGHT_COMPONENTS..][..TILE_HEIGHT_COMPONENTS * width];
let start_x = self.wide_coords.0 * WideTile::WIDTH + x as u16;
let start_y = self.wide_coords.1 * Tile::HEIGHT;
let default_blend = blend_mode == BlendMode::new(Mix::Normal, Compose::SrcOver);
fn fill_complex_paint<T: FineType>(
color_buf: &mut [T],
blend_buf: &mut [T],
has_opacities: bool,
blend_mode: BlendMode,
filler: impl Painter,
) {
if has_opacities {
filler.paint(color_buf);
fill::blend(
blend_buf,
color_buf.chunks_exact(4).map(|e| [e[0], e[1], e[2], e[3]]),
blend_mode,
);
} else {
// Similarly to solid colors we can just override the previous values
// if all colors in the gradient are fully opaque.
filler.paint(blend_buf);
}
}
match fill {
Paint::Solid(color) => {
let color = F::extract_color(color);
// If color is completely opaque we can just memcopy the colors.
if color[3] == F::ONE && default_blend {
for t in blend_buf.chunks_exact_mut(COLOR_COMPONENTS) {
t.copy_from_slice(&color);
}
return;
}
fill::blend(blend_buf, iter::repeat(color), blend_mode);
}
Paint::Indexed(paint) => {
let encoded_paint = &encoded_paints[paint.index()];
match encoded_paint {
EncodedPaint::Gradient(g) => match &g.kind {
EncodedKind::Linear(l) => {
let filler = GradientFiller::new(g, l, start_x, start_y);
fill_complex_paint(
color_buf,
blend_buf,
g.has_opacities,
blend_mode,
filler,
);
}
EncodedKind::Radial(r) => {
let filler = GradientFiller::new(g, r, start_x, start_y);
fill_complex_paint(
color_buf,
blend_buf,
g.has_opacities,
blend_mode,
filler,
);
}
EncodedKind::Sweep(s) => {
let filler = GradientFiller::new(g, s, start_x, start_y);
fill_complex_paint(
color_buf,
blend_buf,
g.has_opacities,
blend_mode,
filler,
);
}
},
EncodedPaint::Image(i) => {
let filler = ImageFiller::new(i, start_x, start_y);
fill_complex_paint(
color_buf,
blend_buf,
i.has_opacities,
blend_mode,
filler,
);
}
EncodedPaint::BlurredRoundedRect(b) => {
let filler = BlurredRoundedRectFiller::new(b, start_x, start_y);
fill_complex_paint(color_buf, blend_buf, true, blend_mode, filler);
}
}
}
}
}
/// Strip at a given x and with a width using the given paint and alpha values.
pub fn strip(
&mut self,
x: usize,
width: usize,
alphas: &[u8],
fill: &Paint,
blend_mode: BlendMode,
paints: &[EncodedPaint],
) {
debug_assert!(
alphas.len() >= width,
"alpha buffer doesn't contain sufficient elements"
);
let blend_buf = &mut self.blend_buf.last_mut().unwrap()[x * TILE_HEIGHT_COMPONENTS..]
[..TILE_HEIGHT_COMPONENTS * width];
let color_buf =
&mut self.color_buf[x * TILE_HEIGHT_COMPONENTS..][..TILE_HEIGHT_COMPONENTS * width];
let start_x = self.wide_coords.0 * WideTile::WIDTH + x as u16;
let start_y = self.wide_coords.1 * Tile::HEIGHT;
fn strip_complex_paint<F: FineType>(
color_buf: &mut [F],
blend_buf: &mut [F],
blend_mode: BlendMode,
filler: impl Painter,
alphas: &[u8],
) {
filler.paint(color_buf);
strip::blend(
blend_buf,
color_buf.chunks_exact(4).map(|e| [e[0], e[1], e[2], e[3]]),
blend_mode,
alphas.chunks_exact(4).map(|e| [e[0], e[1], e[2], e[3]]),
);
}
match fill {
Paint::Solid(color) => {
strip::blend(
blend_buf,
iter::repeat(F::extract_color(color)),
blend_mode,
alphas.chunks_exact(4).map(|e| [e[0], e[1], e[2], e[3]]),
);
}
Paint::Indexed(paint) => {
let encoded_paint = &paints[paint.index()];
match encoded_paint {
EncodedPaint::Gradient(g) => match &g.kind {
EncodedKind::Linear(l) => {
let filler = GradientFiller::new(g, l, start_x, start_y);
strip_complex_paint(color_buf, blend_buf, blend_mode, filler, alphas);
}
EncodedKind::Radial(r) => {
let filler = GradientFiller::new(g, r, start_x, start_y);
strip_complex_paint(color_buf, blend_buf, blend_mode, filler, alphas);
}
EncodedKind::Sweep(s) => {
let filler = GradientFiller::new(g, s, start_x, start_y);
strip_complex_paint(color_buf, blend_buf, blend_mode, filler, alphas);
}
},
EncodedPaint::Image(i) => {
let filler = ImageFiller::new(i, start_x, start_y);
strip_complex_paint(color_buf, blend_buf, blend_mode, filler, alphas);
}
EncodedPaint::BlurredRoundedRect(b) => {
let filler = BlurredRoundedRectFiller::new(b, start_x, start_y);
strip_complex_paint(color_buf, blend_buf, blend_mode, filler, alphas);
}
}
}
}
}
fn apply_blend(&mut self, blend_mode: BlendMode) {
let (source_buffer, rest) = self.blend_buf.split_last_mut().unwrap();
let target_buffer = rest.last_mut().unwrap();
fill::blend(
target_buffer,
source_buffer
.chunks_exact(4)
.map(|e| [e[0], e[1], e[2], e[3]]),
blend_mode,
);
}
fn clip_fill(&mut self, x: usize, width: usize) {
let (source_buffer, rest) = self.blend_buf.split_last_mut().unwrap();
let target_buffer = rest.last_mut().unwrap();
let source_buffer =
&mut source_buffer[x * TILE_HEIGHT_COMPONENTS..][..TILE_HEIGHT_COMPONENTS * width];
let target_buffer =
&mut target_buffer[x * TILE_HEIGHT_COMPONENTS..][..TILE_HEIGHT_COMPONENTS * width];
fill::alpha_composite(
target_buffer,
source_buffer
.chunks_exact(4)
.map(|e| [e[0], e[1], e[2], e[3]]),
);
}
fn clip_strip(&mut self, x: usize, width: usize, alphas: &[u8]) {
let (source_buffer, rest) = self.blend_buf.split_last_mut().unwrap();
let target_buffer = rest.last_mut().unwrap();
let source_buffer =
&mut source_buffer[x * TILE_HEIGHT_COMPONENTS..][..TILE_HEIGHT_COMPONENTS * width];
let target_buffer =
&mut target_buffer[x * TILE_HEIGHT_COMPONENTS..][..TILE_HEIGHT_COMPONENTS * width];
strip::alpha_composite(
target_buffer,
source_buffer
.chunks_exact(4)
.map(|e| [e[0], e[1], e[2], e[3]]),
alphas.chunks_exact(4).map(|e| [e[0], e[1], e[2], e[3]]),
);
}
}
fn pack<F: FineType>(
out_buf: &mut [u8],
scratch: &ScratchBuf<F>,
width: usize,
height: usize,
x: usize,
y: usize,
) {
let base_ix = (y * usize::from(Tile::HEIGHT) * width + x * usize::from(WideTile::WIDTH))
* COLOR_COMPONENTS;
// Make sure we don't process rows outside the range of the pixmap.
let max_height = (height - y * usize::from(Tile::HEIGHT)).min(usize::from(Tile::HEIGHT));
for j in 0..max_height {
let line_ix = base_ix + j * width * COLOR_COMPONENTS;
// Make sure we don't process columns outside the range of the pixmap.
let max_width =
(width - x * usize::from(WideTile::WIDTH)).min(usize::from(WideTile::WIDTH));
let target_len = max_width * COLOR_COMPONENTS;
// This helps the compiler to understand that any access to `dest` cannot
// be out of bounds, and thus saves corresponding checks in the for loop.
let dest = &mut out_buf[line_ix..][..target_len];
for i in 0..max_width {
let src = &scratch[(i * usize::from(Tile::HEIGHT) + j) * COLOR_COMPONENTS..]
[..COLOR_COMPONENTS];
dest[i * COLOR_COMPONENTS..][..COLOR_COMPONENTS].copy_from_slice(&F::to_rgba8(src));
}
}
}
pub(crate) mod fill {
// See https://www.w3.org/TR/compositing-1/#porterduffcompositingoperators for the
// formulas.
use crate::fine::{COLOR_COMPONENTS, FineType, TILE_HEIGHT_COMPONENTS, blend};
use vello_common::peniko::{BlendMode, Compose, Mix};
pub(crate) fn blend<F: FineType, T: Iterator<Item = [F; COLOR_COMPONENTS]>>(
target: &mut [F],
source: T,
blend_mode: BlendMode,
) {
match (blend_mode.mix, blend_mode.compose) {
(Mix::Normal, Compose::SrcOver) => alpha_composite(target, source),
_ => blend::fill::blend(target, source, blend_mode),
}
}
pub(crate) fn alpha_composite<F: FineType, T: Iterator<Item = [F; COLOR_COMPONENTS]>>(
target: &mut [F],
mut source: T,
) {
for strip in target.chunks_exact_mut(TILE_HEIGHT_COMPONENTS) {
for bg_c in strip.chunks_exact_mut(COLOR_COMPONENTS) {
let src_c = source.next().unwrap();
for i in 0..COLOR_COMPONENTS {
bg_c[i] = src_c[i].add(bg_c[i].normalized_mul(src_c[3].one_minus()));
}
}
}
}
}
pub(crate) mod strip {
use crate::fine::{COLOR_COMPONENTS, FineType, TILE_HEIGHT_COMPONENTS, Widened, blend};
use vello_common::peniko::{BlendMode, Compose, Mix};
use vello_common::tile::Tile;
pub(crate) fn blend<
F: FineType,
T: Iterator<Item = [F; COLOR_COMPONENTS]>,
A: Iterator<Item = [u8; Tile::HEIGHT as usize]>,
>(
target: &mut [F],
source: T,
blend_mode: BlendMode,
alphas: A,
) {
match (blend_mode.mix, blend_mode.compose) {
(Mix::Normal, Compose::SrcOver) => alpha_composite(target, source, alphas),
_ => blend::strip::blend(target, source, blend_mode, alphas),
}
}
pub(crate) fn alpha_composite<
F: FineType,
T: Iterator<Item = [F; COLOR_COMPONENTS]>,
A: Iterator<Item = [u8; Tile::HEIGHT as usize]>,
>(
target: &mut [F],
mut source: T,
mut alphas: A,
) {
for bg_c in target.chunks_exact_mut(TILE_HEIGHT_COMPONENTS) {
let masks = alphas.next().unwrap();
for j in 0..usize::from(Tile::HEIGHT) {
let src_c = source.next().unwrap();
let mask_a = F::from_normalized_u8(masks[j]);
let inv_src_a_mask_a = mask_a.normalized_mul(src_c[3]).one_minus();
for i in 0..COLOR_COMPONENTS {
let p1 = bg_c[j * COLOR_COMPONENTS + i].widen() * inv_src_a_mask_a.widen();
let p2 = src_c[i].widen() * mask_a.widen();
bg_c[j * COLOR_COMPONENTS + i] = (p1 + p2).normalize().narrow();
}
}
}
}
}
trait Painter {
fn paint<F: FineType>(self, target: &mut [F]);
}
/// A numeric type that can act as a substitute for another underlying type in case
/// the results are too big. Currently, this is only used for u8, where certain operations
/// are first cast to u16 and then cast back to u8.
pub trait Widened<T: FineType>:
Sized
+ Copy
+ PartialEq<Self>
+ PartialOrd<Self>
+ Add<Self, Output = Self>
+ Mul<Self, Output = Self>
+ Sub<Self, Output = Self>
+ Div<Self, Output = Self>
+ Debug
{
/// Clamp the current value to the boundaries **of the underlying narrowed type**.
fn clamp(self) -> Self;
/// Normalize the current value to the range of the underlying narrowed type.
fn normalize(self) -> Self;
/// Get the minimum between this number and another number.
fn min(self, other: Self) -> Self;
/// Get the maximum between this number and another number.
fn max(self, other: Self) -> Self;
/// Perform a normalizing multiplication between this number and another number.
fn normalized_mul(self, other: Self) -> Self;
/// Cast the current type to its narrowed representation.
fn narrow(self) -> T;
}
impl Widened<Self> for f32 {
#[inline(always)]
fn clamp(self) -> Self {
Self::clamp(self, Self::ZERO, Self::ONE)
}
#[inline(always)]
fn normalize(self) -> Self {
// f32 values are always normalized between 0.0 and 1.0.
self
}
#[inline(always)]
fn min(self, other: Self) -> Self {
Self::min(self, other)
}
#[inline(always)]
fn max(self, other: Self) -> Self {
Self::max(self, other)
}
#[inline(always)]
fn normalized_mul(self, other: Self) -> Self {
self * other
}
#[inline(always)]
fn narrow(self) -> Self {
self
}
}
impl Widened<u8> for u16 {
#[inline(always)]
fn clamp(self) -> Self {
Ord::clamp(self, Self::from(u8::ZERO), Self::from(u8::ONE))
}
#[inline(always)]
fn normalize(self) -> Self {
div_255(self)
}
#[inline(always)]
fn min(self, other: Self) -> Self {
Ord::min(self, other)
}
#[inline(always)]
fn max(self, other: Self) -> Self {
Ord::max(self, other)
}
#[inline(always)]
fn normalized_mul(self, other: Self) -> Self {
(self * other).normalize()
}
#[inline(always)]
fn narrow(self) -> u8 {
debug_assert!(
self <= Self::from(u8::MAX),
"cannot narrow integers larger than u8::MAX"
);
self as u8
}
}
/// A type that can be used as the underlying storage for fine rasterization.
pub trait FineType:
Sized
+ Copy
+ PartialEq<Self>
+ PartialOrd<Self>
+ Add<Self, Output = Self>
+ Mul<Self, Output = Self>
+ Sub<Self, Output = Self>
+ Debug
{
type Widened: Widened<Self>;
/// The number that is considered to be the minimum of the normalized range of this type.
const ZERO: Self;
/// The number that is considered to be in the "center" of the normalized range of this type.
const MID: Self;
/// The number considered to be the maximum of the normalized range of the type.
const ONE: Self;
/// Return the minimum number.
fn min(self, other: Self) -> Self;
/// Return the maximum number.
fn max(self, other: Self) -> Self;
/// Extract the underlying color from a premultiplied color.
fn extract_color(color: &PremulColor) -> [Self; COLOR_COMPONENTS];
/// Convert a normalized u8 integer to this type.
fn from_normalized_u8(num: u8) -> Self;
/// Convert a plain u8 integer to this type.
fn from_u8(num: u8) -> Self;
/// Convert this number to a normalized f32.
fn to_normalized_f32(self) -> f32;
/// Convert this number to a normalized u8.
fn to_normalized_u8(self) -> u8;
/// Convert to this number from a normalized f32.
fn from_normalized_f32(num: f32) -> Self;
/// Get the widened representation of the current number.
fn widen(self) -> Self::Widened;
/// Perform a normalized multiplication between this number and another
#[inline(always)]
fn normalized_mul(self, other: Self) -> Self {
(self.widen() * other.widen()).normalize().narrow()
}
/// Perform a widening multiplication and then divide by a third number.
#[inline(always)]
fn widened_mul_div(self, other: Self, other2: Self) -> Self::Widened {
(self.widen() * other.widen()) / other2.widen()
}
// TODO: These RGBA conversions should be sized to COLOR_COMPONENTS, but will leave that for
// the future.
/// Convert a slice to a RGBA8 slice.
#[inline(always)]
fn to_rgba8(src: &[Self]) -> [u8; COLOR_COMPONENTS] {
[
src[0].to_normalized_u8(),
src[1].to_normalized_u8(),
src[2].to_normalized_u8(),
src[3].to_normalized_u8(),
]
}
/// Convert a RGBA8 slice to a slice of this type.
#[inline(always)]
fn from_rgba8(src: &[u8]) -> [Self; COLOR_COMPONENTS] {
[
Self::from_normalized_u8(src[0]),
Self::from_normalized_u8(src[1]),
Self::from_normalized_u8(src[2]),
Self::from_normalized_u8(src[3]),
]
}
/// Convert a RGBAF32 slice to a slice of this type.
#[inline(always)]
fn from_rgbaf32(src: &[f32]) -> [Self; COLOR_COMPONENTS] {
[
Self::from_normalized_f32(src[0]),
Self::from_normalized_f32(src[1]),
Self::from_normalized_f32(src[2]),
Self::from_normalized_f32(src[3]),
]
}
/// Calculate "one minus" this number, i.e., `Self::ONE - self`.
#[inline(always)]
fn one_minus(self) -> Self {
Self::ONE - self
}
}
impl FineType for u8 {
type Widened = u16;
const ZERO: Self = 0;
const MID: Self = 127;
const ONE: Self = 255;
#[inline(always)]
fn min(self, other: Self) -> Self {
Ord::min(self, other)
}
#[inline(always)]
fn max(self, other: Self) -> Self {
Ord::max(self, other)
}
#[inline(always)]
fn extract_color(color: &PremulColor) -> [Self; COLOR_COMPONENTS] {
color.as_premul_rgba8().to_u8_array()
}
#[inline(always)]
fn from_normalized_u8(num: u8) -> Self {
num
}
#[inline(always)]
fn from_u8(num: u8) -> Self {
num
}
#[inline(always)]
fn to_normalized_f32(self) -> f32 {
f32::from(self) / 255.0
}
#[inline(always)]
fn to_normalized_u8(self) -> u8 {
self
}
#[inline(always)]
fn from_normalized_f32(num: f32) -> Self {
(num * 255.0 + 0.5) as Self
}
#[inline(always)]
fn widen(self) -> Self::Widened {
u16::from(self)
}
}
impl FineType for f32 {
type Widened = Self;
const ZERO: Self = 0.0;
const MID: Self = 0.5;
const ONE: Self = 1.0;
#[inline(always)]
fn min(self, other: Self) -> Self {
Self::min(self, other)
}
#[inline(always)]
fn max(self, other: Self) -> Self {
Self::max(self, other)
}
#[inline(always)]
fn extract_color(color: &PremulColor) -> [Self; COLOR_COMPONENTS] {
color.as_premul_f32().components
}
#[inline(always)]
fn from_normalized_u8(num: u8) -> Self {
Self::from(num) / 255.0
}
#[inline(always)]
fn from_u8(num: u8) -> Self {
Self::from(num)
}
#[inline(always)]
fn to_normalized_f32(self) -> f32 {
self
}
#[inline(always)]
fn to_normalized_u8(self) -> u8 {
(self * 255.0 + 0.5) as u8
}
#[inline(always)]
fn from_normalized_f32(num: f32) -> Self {
num
}
#[inline(always)]
fn widen(self) -> Self::Widened {
self
}
}