在 Vaadin 14 应用程序中为像素密集 ("Retina") 监视器调整“图像”小部件的大小

Size an `Image` widget for pixel-dense ("Retina") monitors in a Vaadin 14 app

我知道如何在 Vaadin 14.1 应用程序的屏幕上呈现 JPEG 图像。

特别是 3 行:

imageReader.setInput( imageInputStream ); // Render the image from the data provided by the `ImageInputStream`.
imageWidget.setWidth( imageReader.getWidth( 0 ) + "px" ); // Get the pixel size of the rendered image.
imageWidget.setHeight( imageReader.getHeight( 0 ) + "px" );

…其中:

private Image createImageWidget ( String mimeType , String fileName , InputStream inputStreamOfImageData )
{
    if ( mimeType.startsWith( "image" ) )
    {
        Image imageWidget = new Image();
        try (
                inputStreamOfImageData ;
                // Auto-close.
        )
        {
            // Get the octets of the image data.
            final byte[] bytes = IOUtils.toByteArray( inputStreamOfImageData );

            // Make the image widget for Vaadin.
            imageWidget.getElement().setAttribute( "src" , new StreamResource( fileName , ( ) -> new ByteArrayInputStream( bytes ) ) );
            imageWidget.setAlt( "File name: " + fileName );

            // Size the image for display in the HTML/CSS for faster rendering without the page layout jumping around in front of user.
            try (
                    ImageInputStream imageInputStream = ImageIO.createImageInputStream( new ByteArrayInputStream( bytes ) ) ;
                    // Auto-close.
            )
            {
                // Apparently, image readers (parsers/decoders) are detected at runtime and loaded via the "Java Service Provider Interface (SPI)" facility.
                final Iterator < ImageReader > imageReaders = ImageIO.getImageReadersByMIMEType( UploadView.MIME_TYPE_JPEG );  // OR, for multiple image types, call `ImageIO.getImageReaders( in )`.
                if ( imageReaders.hasNext() )
                {
                    ImageReader imageReader = imageReaders.next();
                    try
                    {
                        imageReader.setInput( imageInputStream ); // Render the image from the data provided by the `ImageInputStream`.
                        imageWidget.setWidth( imageReader.getWidth( 0 ) + "px" ); // Get the pixel size of the rendered image.
                        imageWidget.setHeight( imageReader.getHeight( 0 ) + "px" );
                    }
                    finally
                    {
                        imageReader.dispose();
                    }
                } else
                {
                    throw new IllegalStateException( "Failed to find any image readers for JPEG. " + "Message # e91ce8e4-0bd3-424d-8f7c-b3f51c7ef827." );
                }
            }
        }
        catch ( IOException e )
        {
            e.printStackTrace();
        }
        return imageWidget;
    }
    // TODO: Log error. Should not reach this point.
    System.out.println( "BASIL - ERROR - Ended up with a null `imageWidget`. " + "Message # 415bd917-67e2-49c3-b39a-164b0f900a3a." );
    return null;
}

我明白,在这 3 行中,我们是在通知网页图片的宽度和高度,而不是让网页在加载图片时发现图片的大小。这使得布局更快,并避免了网页在用户面前跳来跳去的烦恼。

➥ 我的问题是:具有高像素密度的现代显示器,Apple 称之为 Retina 显示器怎么样?

如何让图像有效地显示其所有像素,但在屏幕上占用最少 space?

如果显示器具有老式的低像素密度,则图像在物理屏幕上显示的厘米数应该比在高密度 Retina 显示器上显示的多厘米 space技术上显示所有图像的像素。

在 Vaadin 14 中,Image component is just representation of HTML img 标签。这应该意味着像 em 或相对百分比这样的大小单位应该适用。当您不想对屏幕尺寸做出假设时,这些应该会更好。创建响应式设计的一般经验法则是避免像素大小并使用例如em 或百分比。

但是如果你出于某种原因想要自己执行相对大小计算,你可以通过这种方式获得浏览器的大小window:

    getUI().ifPresent(ui -> ui.getPage().retrieveExtendedClientDetails(receiver -> {
        int bodyWidth = receiver.getBodyClientWidth();
        System.out.println("Width "+bodyWidth);
    }));

在线快速搜索没有发现任何本机浏览器功能可以根据本机像素密度为 <img> 元素提供不同的尺寸。我们所拥有的是可以使用 JavaScript 读取的 window.devicePixelRatio 属性。参见 guide by Mozilla

您可以通过两种不同的方式将其与 executeJs 一起使用 - 动态设置每个元素的大小或将值获取到服务器,然后在服务器上为每个 Image组件。

第一个替代方案看起来像这样:

imageWidget.getElement().executeJs("this.style.width = ([=10=] / window.devicePixelRatio) + 'px';" +
    "this.style.height = ( / window.devicePixelRatio) + 'px';",
  imageWidth, imageHeight);

第二种获取比率并使用它的方法是这样的:

ui.getPage().executeJs("return window.devicePixelRatio;")
  .then(Double.class, ratio -> this.devicePixelRatio = ratio.doubleValue());

// Later, in some other location
imageWidget.setWidth((imageWidth / this.devicePixelRatio) + "px");
imageWidget.setHeight((imageHeight / this.devicePixelRatio) + "px");

编辑:第二种方法的变体,使用 retrieveExtendedClientDetails 而不是 executeJs:

ui.getPage().retrieveExtendedClientDetails(details -> this.devicePixelRatio = details.getDevicePixelRatio());

// Later, in some other location
imageWidget.setWidth((imageWidth / this.devicePixelRatio) + "px");
imageWidget.setHeight((imageHeight / this.devicePixelRatio) + "px");