Soporte HiDPI en Xojo

Xojo soporta desde la release 2016r1 más puntos de resolución por pulgada en las pantallas de las apps de Mac y Windows; y también bajo Linux desde la release 2017r2. Apple denomina a dicha capacidad Retina Display, si bien el término genérico es HiDPI. Para la mayoría de tus apps probablemente simplemente bastará con activar el conmutador Support Retina / HiDPI en el inspector del apartado correspondiente a Shared Build Settings. Cuando lo hagas todos los elementos de la UI sacarán provecho automáticamente de las pantallas HiDPI. A continuación encontrarás en español el artículo disponible originalmente en inglés en el sitio web de desarrolladores de Xojo en este enlace.

En Windows, una buena referencia consiste en los documentos Writing DPI-Aware Desktop and Win32 Applications de Microsoft.

Para que el soporte de pantallas HiDPI sea lo más sencillo posible en aquellas aplicaciones que utilizan gráficos personalizados, existen varios cambios en las clases: Application, Picture, Graphics, Windows y Canvas.

Aspectos generales

Los cambios más significativos son los aplicados en Picture, y que ahora puede contener tres tipos de imágenes: Bitmap, Bitmap inmutable, Vector e Image.

De estos, el tipo Image es el que soporta las pantallas HiDPI. Los otros tipos de Picture funcionan tal y como hacían anteriormente. Para ser más preciso, se tratan del siguiente modo:

  • Bitmap: Un objeto de Picture mutable que es idéntico al que existía con anterioridad al soporte de HiDPI.
  • Bitmap inmutable: bitmap de sólo lectura con una única representación. Si el bitmap es RGB con 8, 16, 24 o 32 bits por píxel entonces habrá sólo una RGBSurface de sólo lectura que puede utilizarse para acceder a los píxeles en bruto que no ha pasado por la conversión de color o el alfa pre-multiplicado (generalmente).
  • Image: Un array de bitmaps mutables o inmutables que pueden cargarse desde un Image Set, un TIFF o bien pueden crearse en tiempo de ejecución. Cuando se dibuja sobre un objeto gráfico, entonces se elige el “mejor” bitmap (tal y como se describe a continuación).

Image Sets

Un elemento de proyecto Image Set puede contener imágenes en tres escalas: 1x, 2x y 3x. Todas las imágenes han de tener la misma relación de aspecto. Por ejemplo si tu imagen 1x es 64×64 @ 72 DPI, entonces la imagen 2x ha de tener la misma relación de aspecto (1:1) y por tanto será de 128×128 @ 72DPI o bien 64×64 @ 144DPI. Asegúrate de que la herramienta utilizada para crear o editar las imágenes ajuste correctamente los DPI (puntos por pulgada) de la imagen. Algunas herramientas (como es el caso de Photoshop) no ajustan los DPI correctamente, lo que causa que Xojo interprete la imagen con el tamaño incorrecto.

Puedes añadir imágenes a tus proyectos añadiendo un Image Set desde el menú Insert > Image.

Se crea un Image Set automáticamente cuando se arrastra un archivo de imagen al proyecto, siempre que esté activada la propiedad HiDPI. Cuando está desactivada dicha propiedad, entonces la acción de arrastrar la imagen sobre el proyecto se limita a añadirla como Picture. Si quieres añadir una imagen a un Image Set cuando está desactivado el soporte HiDPI, entonces tendrás que añadir en primer lugar el Image Set al proeycto y arrastrar a continuación el archivo de imagen sobre el contenedor 1x, 2x o 3x apropiado. En la actualidad no es muy probable que resule necesario el uso de imágenes 3x.

La imagen 3x no se utiliza en ninguna de las resoluciones de macOS. Puede que se utilice en algunas resoluciones de Windows 10 ajustadas con un escalado del 300%.

Las imágenes añadidas al proyecto en Image Sets son tratadas como Imágenes y por tanto siguen las reglas descritas en la sección Imagen.

Los archivos de tipo Picture existentes en los proyectos existentes son tratados como 1x y se escalarán en caso necesario. Probablemente querrás actualizar tus proyectos con múltiples tamaños de tus imágenes. Deberías de crear un Image Set con el mismo nombre de la imagen original y añadir imágenes a los contenedores 1x, 2x y 3x a medida que sea necesario. El código que haga referencia a dichas imágenes continuará funcionando sin más, dado que el nombre usado para la imagen se corresponde ahora con el Image Set.

Imagen

Un Picture que sea en realidad un tipo Imagen puede estar compuesto por uno o más mapas de bits. Cuando se utiliza una pantalla HiDPI se utiliza la imagen que mejor se adecue a la pantalla, siguiendo para ello estas reglas:

  1. El algoritmo itera por las imágenes indexadas para encontrar la imagen que más se aproxime a la cantidad de píxeles en el destino, y prefiriendo la siguiente imagen de mayor tamaño en el caso de que no se halle una correspondencia exacta.
  2. En el caso de que coincidan múltiples imágenes, entonces se elegirá aquella cuyos DPI estén más próximos a la escala de destino.

La clase Picture dispone de varias propiedades que se han actualizado para que funcionen correctamente con Imágenes. Esta tabla muestra qué propiedes pueden utilizarse en función del tipo de imagen:

Propiedad de PictureBitmap MutableBitmap InmutableVectorImagen
GraphicsVálidoNilNilNil
DepthIndicado por el usuarioIndicado por el archivo00
HasAlphaChannelIndicado por el usuarioIndicado por el archivoFalseFalse
HeightPíxelesPíxelesPuntosPuntos
HorizontalResolutionIndicado por el usuarioIndicado por el archivo72 (1)72
ImageCount000Indicado por el archivo
MaskIndicado por el usuarioNilNilNil
ObjectsNilNilVálidoNil
RGBSurfaceVálidoNilNilNil
TransparentIndicado por el usuario000
VerticalResolutionIndicado por el usuarioIndicado por el archivo72 (1)72
WidthPíxelesPíxelesPuntosPuntos

(1) No relevante para las imágenes de tipo vector

Esta tabla indica qué métodos pueden utilizarse con los diversos tipos de imágenes:

Picture MethodBitmapImmutable BitmapVectorImage
ApplyMaskVálidoUnsupportedOperationExceptionUnsupportedOperationExceptionUnsupportedOperationException
CopyMaskVálidoVálidoUnsupportedOperationExceptionUnsupportedOperationException
CopyOSHandleVálidoVálidoUnsupportedOperationExceptionUnsupportedOperationException si no es NSImage
GetDataVálidoVálidoUnsupportedOperationExceptionUnsupportedOperationException si lossy
SaveVálidoVálidoVálidoUnsupportedOperationException si lossy

Notas sobre Picture

  • Los Picture añadidos al proyecto, ya sean cargados desde disco, mediante arrastrar y soltar o generados a partir de datos se cargarán como imágenes si el formato lo soporta. Esto preserva en mayor medida los espacios de color, carga las múltiples resoluciones si es que el formato lo soporta y también reduce el uso de memoria. Los formatos de bitmap sencillos se cargarán como Bitmaps inmutables.
  • Las propiedades ImageCount e IndexedImage proporcionan un modo de acceder a los diferentes bitmap que componen una imagen. Es posible que dicha propiedad contenga el valor cero.
  • Los Picture del proyecto se compondrán en tiemp ode ejecución si tienen una máscara o su transparencia definida. En tiempo de ejecución el gráfico se cargará como una imagen.
  • Usa los Image Set para añadir imágenes con múltiples resoluciones (y la misma relación de aspecto) al proyecto.
  • Cuando se dibuja un Picture mediante DrawPicture o se utiliza en un elemento de la UI, como pueda ser MenuItem, entonces se utiliza la mejor representación disponible. Los Vectores simplemente se rasterizan (convertidos a mapas de bits) al tamaño indicado.
  • En el caso de los Pictures usados en los elementos de la interfaz de usuario, como puedan ser los MenuItems o los BevelButton, la representación de imagen se recalcula automáticamente cuando cambie el factor de escala.
  • Para convertir de Image a Bitmap, se puede crear un bitmap con la dimensión de píxeles deseada y ldibujando uego la imagen fuente. Esto viene a imitar cómo puede convertirse un bitmap con una máscara en un bitmap con un canal alfa.
  • El uso de RectControl.DrawInto puede resultar en escalar la imagen más o menos si el factor de escala del buffer para la ventana del control no se corresponde con el factor de escala del buffer de destino.

Cambios en la API del framework

Application

La clase Application tiene una nueva propiedad ajustable en el editor y que permite activar el soporte HiDPI. Esta propiedad se encuentra en el apartado Shared Build Settings del proyecto.

La clase Picture incorpora varios cambios para proporcionar soporte a las imágenes HiDPI.

SupportsHiDPI As Boolean (read-only)

Sólo puede definirse esta propiedad en el Inspector para el objeto App. Ajústala en ON para que la app sea HiDPI. Por omisión esta definida a False.

Se puede consultar el valor de dicha propiedad en tiempo de ejecución:

If App.SupportsHiDPI Then
' HiDPI is enabled
Else
' HiDPI is not enabled
End If

Cuando se pone a OFF la propiedad SupportsHiDPI, el framework continuará funcionando tal y como lo hacía anteriormente Las imágenes que utilicen @2x o @3x en su nombre tendrán sus PPI ajustados en consonancia (144ppi, 216ppi).

Puedes activar o desactivar la propiedad SupportsHiDPI de forma dinámica mediante el uso de un Script del IDE. Este la activaría, por ejemplo:

PropertyValue("App.SupportsHiDPI") = "True"

Utiliza el texto ”False” para desactivarlo.

Cuando la propiedad SupportsHiDPI está activa, el framework no cambia los PPI de la imagen @2x o @3x cuando esta se carga. Depende de ti ajustar los PPI (algo que se realiza prácticamente de forma automática cuando se utiliza un Image Set). La razón para ello es que si necesitas acceder a los PPI de la imagen original, podrás hacerlo cargando manualmente la imagen y ajustando sus PPI.

Si quieres ajustar los PPI automáticamente basándote para ello en el nombre de la imagen, puedes utilizar este método:

Sub SetImageResolutionFromName(p As Picture, filename As String)
Dim r As New RegEx
r.SearchPattern = "@([0-9.]+)x\."

Dim rm As RegExMatch = r.Search(filename)
If rm <> Nil And rm.SubExpressionCount > 0 Then
Dim n As Double = CDbl(rm.SubExpressionString(1))
p.HorizontalResolution = 72 n
p.VerticalResolution = 72
n
End If
End Sub

Picture

La clase Picture incorpora varios cambios para proporcionar soporte a las imágenes HiDPI.

Constructor(width As Integer, height As Integer, bitmaps() As Picture)

Este nuevo constructor crea una umagen a partir de uno o más mapas de bits (bitmaps). La altura y ancho se indican en puntos. Los bitmaps se copian tras su creación. Todos los bitmaps tienen que tener la misma relación de aspecto. Los DPI de un bitmap se calculan en píxeles a partir del tamaño de punto y el tamaño del bitmap. Los Picture creados con este constructor tienen su propiedad Type ajustada a Types.Image.

Eleva exepciones en los siguientes casos:

  • InvalidArgumentException si el ancho o alto son inferiores o igual a cero.
  • InvalidArgumentException si cualquiera de los bitmap no es en realidad un bitmap.
  • InvalidArgumentException si cualquiera de los bitmap no tienen la misma relación de aspecto que Width y Height.
  • InvalidArgumentException si ‘bitmaps’ está vacío.
  • NilObjectException si ‘bitmaps’ es Nil.
  • NilObjectException si cualquiera de los bitmaps son Nil.

Property Type As Types (read-only)

El tipo de este Picture.

Tipos de Enumerador

Un enumerador que contiene todos los tipos de imagen válidos.

ImageUna imagen para su uso con pantallas HiDPI.
VectorUn gráfico vectorial.
MutableBitmapUna imagen bitmap que puede modificarse.
ImmutableBitmapUna imagen bitmap que no puede modificarse

Enum HandleType.NSImage

Un objeto NSImage que se ha liberado de modo que no es necesario realizar un release explícito. Esto está soportado para todo tipo de Pictures, incluyendo las imágenes vectoriales. Soportado en las aplicaciones macOS con interfaz de usuario (GUI).

BestRepresentation(width As Integer, height As Integer, scale As Double) As Picture

Calcula cual es la mejor imagen a utilizar para dibujar al tamaño indicado.

El algoritmo itera por las imágenes indexadas, encontrando en primer lugar la imagen con la cantidad de píxeles más próxima a la de destino, prefiriendo la siguiente imagen más grande en el caso de que no se encuentre una correspondencia exacta. Si existen múltiples correspondencias, entonces se elige la imagen con los DPI más próximos a la escala de destino. Es posible que esta función devuelva Self.

Eleva un InvalidArgumentException si el ancho, la altura o la escala son inferiores o igual a cero.

CopyOSHandle(width As Integer, height As Integer, scale As Double, type As Picture.HandleType) As Ptr

Devuelve un manejador de imagen específico de la plataforma que mejor se corresponda para su dibujado a la resolución indicada, utilizando para ello la misma lógica que BestRepresentation.

El manejador devuelto es una copia de los datos subyacentes y no se modificará en el caso de que se modifique el Picture propiamente dicho. De igual modo, dado que es una copia, es tu responsabilidad liberarlo utilizando para ello la API adecuada del SO.

Eleva excepciones en estos casos:

  • InvalidArgumentException si el ancho, la altura o la escala son menos o igual de cero.
  • UnsupportedFormatException en el caso de que el tipo de manejador no esté soportado.

Graphics

La clase Graphics tiene los siguientes cambios.

Property ScaleX As Double
Property ScaleY As Double

Se utiliza el factor de escala cuando se convierte las coordenadas del espacio de usuario a las coordenadas del espacio de trabajo. Estas pueden modificarse en tiemp ode ejecución y debes ser superiores a cero.

Cuando se inicia un objeto Graphics la escala se define con la transformada actual utilizada para convertir las coordenadas de espacio de usuario a las coordenadas del espacio de trabajo. Por ejemplo, el objeto gráfico en un Canvas pasado a un evento Paint puede tener las propiedades ScaleX y ScaleY definidas a 2 cuando se dibuja sobre una pantalla retina.

Para un Picture de mapa de bits, las propiedades ScaleX y ScaleY son siempre 1.

Estas propiedades no se actualizan automáticamente en el caso de que se produzcan cambios en el factor de escala del espacio de trabnajo del control. Sin embargo, se actualizan automáticamente cuando se llama a Graphics.NextPage de modo que se vuelva al estado deseado por el sistema para la impresión de dicha página.

La alteración de estas propiedades alterará el alto y ancho del objeto. Indicando un valor de escala superior resultará en un tamaño menor y viceversa. Cuando se crea un nuevo objeto gráfico como resultado de llamara a Clip, el objeto resultante tiene la misma escala de factor que el original. Las modificaciones subsiguientes en la escala de un objeto no impactan sobre el otro, sin embargo.

Graphics.Pixel

Deprecado.

Window

La clase Window tiene los siguientes cambios.

Event Sub ScaleFactorChanged()

Ha cambniado el factor de escala del espacio de trabajo para esta ventana y la aplicación debería de invalidar los mapas de bits cacheados u otro estado relevante. Esto solo se produce en las apps Mac.

Property ScaleFactor As Double (read-only)

El factor de escala usado cuando se convierten las coordenadas del espacio de usuario a las coordenadas del espacio de trabajo para esta ventana.

BitmapForCaching(width As Integer, height As Integer) As Picture

Devuelve un mapa de bits que está configurado correctamente para usar como cache para el contenido a dibujar en la ventana.

Eleva una excepción en los siguientes casos:

  • InvalidArgumentException si el ancho, el alto o la escala son inferiores o iguales a cero.
  • OutOfMemoryException si no se puede reservar espacio para la imagen.

Canvas

Event Sub ScaleFactorChanged()

Ha cambiado el factor de escala para el espacio de trabajo y el canvas debería de invalidar cualquier mapa de bits cacheados u otros estados relevantes. Este sólo se invoca en las apps de macOS.

Código de ejemplo

Si tienes un Image Set con múltiples imágenes para HiDPI y quieres dividir cada una de las imágenes que contiene, puedes hacerlo así:

Dim g As Graphics
Dim p, pics() As Picture

‘ Break the Image into its component pictures
‘ Also copy the horizontal/vertical resolution and scale factors
‘ so that the pictures draw properly when used later.
For i As Integer = 0 To MyImageSet.ImageCount – 1
p = New Picture(MyImageSet.IndexedImage(i).Width, MyImageSet.IndexedImage(i).Height)
p.HorizontalResolution = MyImageSet.IndexedImage(i).HorizontalResolution
p.VerticalResolution = MyImageSet.IndexedImage(i).VerticalResolution
pics.Append(p)
g = p.Graphics
g.ScaleX = p.HorizontalResolution / 72
g.ScaleY = p.VerticalResolution / 72
g.DrawPicture(MyImageSet.IndexedImage(i), 0, 0)
Next

Si posteriormente modificas las imágenes en el array y quieres recrearlo como una imagen, entonces puedes hacerlo utilizando el constructor así:

' Recreate the image from the component pictures
p = New Picture(MyImageSet.Width, MyImageSet.Height, pics)

Otra Información

Problemas conocidos con Windows HiDPI

    • No se escala el HTMLViewer.
    • No se escala MediaPlayer.
Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *