render: scale image intrinsic size by the selected srcset density
The srcset picker already normalized every candidate to a pixel density (x descriptor, or w / source-size) to choose one, then threw the density away, so layout sized the image from the file's raw pixels. Every 2x responsive asset occupied twice its design size. Return the picked candidate's density alongside its URL (img srcset, data-srcset, and picture source paths alike) and divide the fetched intrinsic dimensions by it, so layout sees CSS px. Verified: srcset "x.png 2x" of a 648px file lays out at 324 CSS px and "648w" with sizes=300px at 300, while a bare src stays at raw size; no regressions on the image-heavy spot set (a bbc delta traced to homepage content drift against a days-old baseline, 8.0 vs 7.8 percent when recaptured same-time); obstacle course 33/33.
This commit is contained in:
1 parent
35cbbff9b2
commit
c29c64fc57
1 file changed
+25
-15
@@ -230,7 +230,7 @@ pub fn paint_dom(tree: &DomTree, viewport: (f32, f32), base_url: Option<&str>) -
|
|||||||
}
|
}
|
||||||
|
|
||||||
if name.local.as_ref() == "img" {
|
if name.local.as_ref() == "img" {
|
||||||
if let Some(src) = resolve_img_url(tree, nid) {
|
if let Some((src, _density)) = resolve_img_url(tree, nid) {
|
||||||
let painted = paint_image(&src, base_url, &rect, style.object_fit, &mut pixmap, &mut image_cache);
|
let painted = paint_image(&src, base_url, &rect, style.object_fit, &mut pixmap, &mut image_cache);
|
||||||
// Fall back to alt text only when the image itself did not paint.
|
// Fall back to alt text only when the image itself did not paint.
|
||||||
if !painted {
|
if !painted {
|
||||||
@@ -883,11 +883,14 @@ fn collect_image_intrinsics(
|
|||||||
if node.as_element().map(|e| e.local.as_ref() != "img").unwrap_or(true) {
|
if node.as_element().map(|e| e.local.as_ref() != "img").unwrap_or(true) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let Some(url) = resolve_img_url(tree, nid) else { continue };
|
let Some((url, density)) = resolve_img_url(tree, nid) else { continue };
|
||||||
let Some(bytes) = fetch_bytes(&url, base_url, cache) else { continue };
|
let Some(bytes) = fetch_bytes(&url, base_url, cache) else { continue };
|
||||||
if let Some((w, h)) = image_dimensions(&bytes) {
|
if let Some((w, h)) = image_dimensions(&bytes) {
|
||||||
if w > 0 && h > 0 {
|
if w > 0 && h > 0 {
|
||||||
out.insert(nid, (w as f32, h as f32));
|
// A 2x (or w-descriptor) candidate's raw pixels are density
|
||||||
|
// times its CSS size; divide so layout sees CSS px, or every
|
||||||
|
// responsive image occupies twice its design size.
|
||||||
|
out.insert(nid, (w as f32 / density, h as f32 / density));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -902,18 +905,18 @@ fn collect_image_intrinsics(
|
|||||||
/// resolve the same URL the browser would end up with: a matching `<picture>`
|
/// resolve the same URL the browser would end up with: a matching `<picture>`
|
||||||
/// source first, then a real candidate from `srcset`/`data-srcset`, then a
|
/// source first, then a real candidate from `srcset`/`data-srcset`, then a
|
||||||
/// non-inline `src`/`data-*` URL, then any `src` (an inlined data: image).
|
/// non-inline `src`/`data-*` URL, then any `src` (an inlined data: image).
|
||||||
fn resolve_img_url(tree: &DomTree, nid: obscura_dom::tree::NodeId) -> Option<String> {
|
fn resolve_img_url(tree: &DomTree, nid: obscura_dom::tree::NodeId) -> Option<(String, f32)> {
|
||||||
let node = tree.get_node(nid)?;
|
let node = tree.get_node(nid)?;
|
||||||
// A <picture>'s preceding, type/media-matching <source> wins over the
|
// A <picture>'s preceding, type/media-matching <source> wins over the
|
||||||
// <img>'s own attributes (HTML "update the source set").
|
// <img>'s own attributes (HTML "update the source set").
|
||||||
if let Some(url) = picture_source_url(tree, nid) {
|
if let Some(pick) = picture_source_url(tree, nid) {
|
||||||
return Some(url);
|
return Some(pick);
|
||||||
}
|
}
|
||||||
let sizes = node.get_attribute("sizes");
|
let sizes = node.get_attribute("sizes");
|
||||||
for a in ["srcset", "data-srcset"] {
|
for a in ["srcset", "data-srcset"] {
|
||||||
if let Some(v) = node.get_attribute(a) {
|
if let Some(v) = node.get_attribute(a) {
|
||||||
if let Some(u) = best_srcset_candidate(v, sizes) {
|
if let Some(pick) = best_srcset_candidate(v, sizes) {
|
||||||
return Some(u);
|
return Some(pick);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -923,7 +926,7 @@ fn resolve_img_url(tree: &DomTree, nid: obscura_dom::tree::NodeId) -> Option<Str
|
|||||||
if let Some(v) = node.get_attribute(a) {
|
if let Some(v) = node.get_attribute(a) {
|
||||||
let v = v.trim();
|
let v = v.trim();
|
||||||
if !v.is_empty() && !v.starts_with("data:") {
|
if !v.is_empty() && !v.starts_with("data:") {
|
||||||
return Some(v.to_string());
|
return Some((v.to_string(), 1.0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -932,7 +935,7 @@ fn resolve_img_url(tree: &DomTree, nid: obscura_dom::tree::NodeId) -> Option<Str
|
|||||||
if let Some(v) = node.get_attribute(a) {
|
if let Some(v) = node.get_attribute(a) {
|
||||||
let v = v.trim();
|
let v = v.trim();
|
||||||
if !v.is_empty() {
|
if !v.is_empty() {
|
||||||
return Some(v.to_string());
|
return Some((v.to_string(), 1.0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -944,7 +947,7 @@ fn resolve_img_url(tree: &DomTree, nid: obscura_dom::tree::NodeId) -> Option<Str
|
|||||||
/// first supported one (matching `type` and `media`), per WebKit's
|
/// first supported one (matching `type` and `media`), per WebKit's
|
||||||
/// `HTMLImageElement::bestFitSourceFromPictureElement`. `None` means no source
|
/// `HTMLImageElement::bestFitSourceFromPictureElement`. `None` means no source
|
||||||
/// applied and the caller should fall back to the `<img>`'s own attributes.
|
/// applied and the caller should fall back to the `<img>`'s own attributes.
|
||||||
fn picture_source_url(tree: &DomTree, img_nid: obscura_dom::tree::NodeId) -> Option<String> {
|
fn picture_source_url(tree: &DomTree, img_nid: obscura_dom::tree::NodeId) -> Option<(String, f32)> {
|
||||||
let img = tree.get_node(img_nid)?;
|
let img = tree.get_node(img_nid)?;
|
||||||
let parent = img.parent?;
|
let parent = img.parent?;
|
||||||
let is_picture = tree
|
let is_picture = tree
|
||||||
@@ -1007,7 +1010,11 @@ const SRCSET_VIEWPORT_W: f32 = 1280.0;
|
|||||||
/// width), treat `x` descriptors as-is and a bare candidate as `1x`, then pick
|
/// width), treat `x` descriptors as-is and a bare candidate as `1x`, then pick
|
||||||
/// the smallest density at least the device pixel ratio (1 at DPR 1), else the
|
/// the smallest density at least the device pixel ratio (1 at DPR 1), else the
|
||||||
/// largest available.
|
/// largest available.
|
||||||
fn best_srcset_candidate(srcset: &str, sizes: Option<&str>) -> Option<String> {
|
/// Returns the picked candidate URL and its pixel density. The density is the
|
||||||
|
/// x-descriptor (or, for w-descriptors, width / source-size): the factor the
|
||||||
|
/// file's raw pixels must be divided by to get CSS px. Laying out with raw
|
||||||
|
/// pixels made every 2x responsive image occupy twice its design size.
|
||||||
|
fn best_srcset_candidate(srcset: &str, sizes: Option<&str>) -> Option<(String, f32)> {
|
||||||
const DPR: f32 = 1.0;
|
const DPR: f32 = 1.0;
|
||||||
let source_size = source_size_px(sizes);
|
let source_size = source_size_px(sizes);
|
||||||
let mut cands: Vec<(f32, String)> = Vec::new();
|
let mut cands: Vec<(f32, String)> = Vec::new();
|
||||||
@@ -1055,9 +1062,12 @@ fn best_srcset_candidate(srcset: &str, sizes: Option<&str>) -> Option<String> {
|
|||||||
let pick = cands
|
let pick = cands
|
||||||
.iter()
|
.iter()
|
||||||
.find(|(d, _)| *d >= DPR)
|
.find(|(d, _)| *d >= DPR)
|
||||||
.map(|(_, u)| u.clone())
|
.map(|(d, u)| (u.clone(), *d))
|
||||||
.unwrap_or_else(|| cands.last().unwrap().1.clone());
|
.unwrap_or_else(|| {
|
||||||
Some(pick)
|
let (d, u) = cands.last().unwrap();
|
||||||
|
(u.clone(), *d)
|
||||||
|
});
|
||||||
|
Some((pick.0, pick.1.max(0.01)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Approximate the CSS px size an image will be displayed at, from its `sizes`
|
/// Approximate the CSS px size an image will be displayed at, from its `sizes`
|
||||||
|
|||||||
Reference in new issue
Block a user