LibGfx+LibWeb: Use mipmaps for downscaling images

This changes Gfx::ScalingMode to reflect the three modes of scaling we
support using Skia, which makes it a bit easier to reason about the mode
to select. New is ::BilinearMipmap, which uses linear interpolation
between mipmap levels to produce higher quality downscaled images.

The cubic resampling options Mitchell and its sibling CatmullRom both
produced weird artifacts or resulted in a worse quality than
BilinearMipmap when downscaling. We might not have been using these
correctly, but the new ::BilinearMipmap method seems to mirror what
Chrome uses for downscaled images.
This commit is contained in:
Jelle Raaijmakers 2025-11-11 16:43:41 +01:00 committed by Alexander Kalenik
parent 7544066c0c
commit 3f6cbeb87e
Notes: github-actions[bot] 2025-11-12 15:00:21 +00:00
23 changed files with 66 additions and 29 deletions

View file

@ -455,17 +455,19 @@ struct TextDecorationThickness {
// FIXME: Find a better place for this helper.
inline Gfx::ScalingMode to_gfx_scaling_mode(ImageRendering css_value, Gfx::IntRect source, Gfx::IntRect target)
{
if (source.size() == target.size())
return Gfx::ScalingMode::None;
switch (css_value) {
case ImageRendering::Auto:
case ImageRendering::HighQuality:
case ImageRendering::Smooth:
if (target.width() < source.width() || target.height() < source.height())
return Gfx::ScalingMode::BoxSampling;
return Gfx::ScalingMode::BilinearBlend;
if (target.width() < source.width() && target.height() < source.height())
return Gfx::ScalingMode::BilinearMipmap;
return Gfx::ScalingMode::Bilinear;
case ImageRendering::CrispEdges:
return Gfx::ScalingMode::NearestNeighbor;
case ImageRendering::Pixelated:
return Gfx::ScalingMode::SmoothPixels;
return Gfx::ScalingMode::NearestNeighbor;
}
VERIFY_NOT_REACHED();
}

View file

@ -188,7 +188,7 @@ WebIDL::ExceptionOr<void> CanvasRenderingContext2D::draw_image_internal(CanvasIm
auto scaling_mode = Gfx::ScalingMode::NearestNeighbor;
if (drawing_state().image_smoothing_enabled) {
// FIXME: Honor drawing_state().image_smoothing_quality
scaling_mode = Gfx::ScalingMode::BilinearBlend;
scaling_mode = Gfx::ScalingMode::BilinearMipmap;
}
if (auto* painter = this->painter()) {

View file

@ -214,11 +214,11 @@ static ErrorOr<NonnullRefPtr<Gfx::Bitmap>> crop_to_the_source_rectangle_with_for
// The "high" value indicates a preference for a high level of image interpolation quality. High-quality image interpolation may be more computationally expensive than lower settings.
case Bindings::ResizeQuality::Medium:
// The "medium" value indicates a preference for a medium level of image interpolation quality.
scaling_passes.append(ScalingPass { .mode = Gfx::ScalingMode::BoxSampling, .width = output_width, .height = output_height });
scaling_passes.append(ScalingPass { .mode = Gfx::ScalingMode::BilinearMipmap, .width = output_width, .height = output_height });
break;
case Bindings::ResizeQuality::Low:
// The "low" value indicates a preference for a low level of image interpolation quality. Low-quality image interpolation may be more computationally efficient than higher settings.
scaling_passes.append(ScalingPass { .mode = Gfx::ScalingMode::BilinearBlend, .width = output_width, .height = output_height });
scaling_passes.append(ScalingPass { .mode = Gfx::ScalingMode::Bilinear, .width = output_width, .height = output_height });
break;
case Bindings::ResizeQuality::Pixelated: {
// The "pixelated" value indicates a preference for scaling the image to preserve the pixelation of the original as much as possible, with minor smoothing as necessary to avoid distorting the image when the target size is not a clean multiple of the original.
@ -237,7 +237,7 @@ static ErrorOr<NonnullRefPtr<Gfx::Bitmap>> crop_to_the_source_rectangle_with_for
scaling_passes.append(ScalingPass { .mode = Gfx::ScalingMode::NearestNeighbor, .width = source_width * width_multiple, .height = source_height * height_multiple });
// then scale it the rest of the way to the target size using bilinear interpolation.
scaling_passes.append(ScalingPass { .mode = Gfx::ScalingMode::BilinearBlend, .width = output_width, .height = output_height });
scaling_passes.append(ScalingPass { .mode = Gfx::ScalingMode::Bilinear, .width = output_width, .height = output_height });
} break;
}
for (ScalingPass& scaling_pass : scaling_passes) {