astro-imagetools
astro-imagetools copied to clipboard
Images on high resolution displays
What would be the best way to have images with fixed height, that are of 2x resolution (on high DPI displays)?

If I set height={60} the image will be 60px high. This is great, but in case of a high resolution display the images will look blurry.
I now made a one breakpoint that is high resolution and restrict the the hight to a smaller dimension with css.
<Picture src="/src/images/customers/gspan.jpg" alt="Hotel Gspan" placeholder={'tracedSVG'} layout="fixed"
breakpoints={[400]} />
::global(img) {
height: 60px;
width: 100%;
}
This works but it seams very convoluted and doesn't take the images width in to account. Wide images are going to be lower resolution and tall images to high resolution. Is there an easier way of doing this?
I may be mistaken but I don't think you can do this directly.
You could try create a new Astro component which will read the source file from your disk to get the image dimension and use that information to pass the correct information to the <Picture>
element.
Here is some related code which can load the file and get it's dimensions: https://github.com/RafidMuhymin/astro-imagetools/blob/414b858fc2080cdb7d9ae8d6be90e787a9e5ad2e/packages/astro-imagetools/api/utils/codecs.js#L7-L12
So perhaps you could create something like this: (⚠️not tested)
fixed-height-picture.astro
:
---
import fs from "node:fs";
import { extname } from "node:path";
import * as codecs from "@astropub/codecs";
import { Picture } from "astro-imagetools/components";
import { getSrcPath } from 'astro-imagetools/api/utils/getSrcPath.js'
const getImageDimensions = async (src) => {
const path = getSrcPath(src);
const extension = extname(path).slice(1);
const imageFormat = extension === "jpeg" ? "jpg" : extension;
const buffer = fs.readFileSync(path);
const decodedImage = await codecs.jpg.decode(buffer);
return {
width: decodedImage.width,
height: decodedImage.height,
};
}
const { fixedheight, ...pictureProps } = Astro.props; // include a property called 'fixedheight'
const { src } = pictureProps;
const { height, width } = await getImageDimensions(src);
const desiredWidth = (fixedheight / height) * width;
const breakpoints = [ desiredWidth, desiredWidth * 2 ]; // calculate the desired breakpoint sizes (i.e. 1x and 2x)
pictureProps.breakpoints = breakpoints;
---
<Picture { ...pictureProps } />
and then you could use it like this:
---
import FixedHeightPicture from "../??/fixed-height-picture.astro";
---
<FixedHeightPicture src="/src/images/customers/gspan.jpg" alt="Hotel Gspan" placeholder={'tracedSVG'} layout="fixed"
fixedheight={60} />
Thanks! Love the result, but I'm surprised how much work it's to get there. In my opinion this should be the default behaviour for a fixed layout and height. @RafidMuhymin
The code above ended up like this:
---
import fs from "node:fs";
import { extname, join } from "node:path";
import * as codecs from "@astropub/codecs";
import { Picture } from "astro-imagetools/components";
const getImageDimensions = async (src) => {
const filepath = join(process.cwd(), src) //
const extension = extname(filepath).slice(1);
const imageFormat = extension === "jpeg" ? "jpg" : extension;
const buffer = fs.readFileSync(filepath);
const decodedImage = await codecs[imageFormat].decode(buffer);
return {
width: decodedImage.width,
height: decodedImage.height,
};
}
const { height: propHeight, ...pictureProps } = Astro.props;
const { src } = pictureProps;
const { height, width } = await getImageDimensions(src);
const desiredWidth = Math.floor((propHeight / height) * width);
const breakpoints = [desiredWidth, desiredWidth * 2]; // calculate the desired breakpoint sizes (i.e. 1x and 2x)
pictureProps.breakpoints = breakpoints;
pictureProps.sizes = `${desiredWidth}px`
---
<Picture { ...pictureProps } />
I couldn't use getSrcPath
because it is not exported and I set sizes, that the browser picks the right resolution.
The impact is quite dramatic! 😊
DPR 1
DPR 2