Redimensionar imagenes y reducir el tamaño de los pdf en Business Central OnPremise

0
72

Al plasmar imágenes en un informe de Business Central, ya sean imágenes cargadas en un campo de tipo blob como imágenes de productos o sean imágenes externas que podamos tener en nuestro sistema de archivos, se presenta el reto del tamaño y calidad de las imágenes.

Normalmente, durante mucho tiempo, siempre he pretendido que fuera el cliente el que se preocupara del peso de dichos archivos con el fin de que el report, al imprimirlo, no tomara un tiempo infinito en renderizar dichas imágenes y, además, en el caso de ser documentos que se remiten mediante correo electrónico, evitar que el peso del archivo pdf que genere sea demasiado grande como para permitir ese envío.

Recientemente encontré un método, mediante dotNet, para conseguir modificar la calidad de las imágenes y asi reducir el peso y la carga de renderización de los informes. De este modo, el cliente ya no tiene que preocuparse por la modificación de las imágenes que importa en la aplicación ni por las que se encuentren en su sistema de archivos (que podrían llegarle de forma externa y no tener manera viable de modificarlas de forma eficiente).

Para todo ello, en primer lugar se requiere de la declaración de las variables necesarias:

dotnet
{
    assembly(System.Drawing)

    {
        Version = '4.0.0.0';
        Culture = 'Neutral';
        PublicKeyToken = 'b03f5f7f11d50a3a';
        type(System.Drawing.Bitmap; Bitmap) { }
        type(System.Drawing.Imaging.ImageFormat; ImageFormat) { }
    }
}

Como nota, previo a las declaraciones se deben declarar los probings paths que son los directorios donde se encuentran las Dll’s a las que queremos hacer referencia. Para ello, desde Visual Studio Code:

Archivo>Preferencias>Configuración>Extensiones>AL Language Extension>Assembly Probing Paths.
(También se puede buscar directamente este ultimo punto y aparecerá)

Habrá que añadir:
C:/Windows/Assembly/

De paso, también:
C:\Program Files\Microsoft Dynamics 365 Business Central\180\Service\Add-ins

Con los Probing Paths configurados y las declaraciones realizadas ya se puede comenzar con el código.

La función que se encarga de realizar la redimensión de un fichero de imagen es la siguiente:

    PROCEDURE ResizeImage(ServerFileName: Text; NewSize: Integer);
    VAR
        BitMapOriginal: DotNet Bitmap;
        BitmapResized: DotNet Bitmap;
        ImageFormat: DotNet ImageFormat;
        NewWidth: Integer;
        NewHeight: Integer;
        AspectRatio: Decimal;

        OutStr: OutStream;
    BEGIN
        // Cargar la imagen
        CLEAR(BitMapOriginal);
        CLEAR(BitmapResized);
        BitMapOriginal := BitMapOriginal.Bitmap(ServerFileName);
        
        // Calcular las nuevas dimensiones de la imagen
        IF BitMapOriginal.Width > BitMapOriginal.Height THEN BEGIN
            AspectRatio := BitMapOriginal.Width / BitMapOriginal.Height;
            NewWidth := NewSize;
            NewHeight := ROUND(NewSize / AspectRatio, 1, '=');
        END ELSE BEGIN
            AspectRatio := BitMapOriginal.Height / BitMapOriginal.Width;
            NewHeight := NewSize;
            NewWidth := ROUND(NewSize / AspectRatio, 1, '=');
        END;

        // Redimensionar la imagen
        BitmapResized := BitmapResized.Bitmap(BitMapOriginal, NewWidth, NewHeight);
        

        CLEAR(TempBlob);
        TempBlob.CREATEOUTSTREAM(OutStr);
        BitmapResized.Save(OutStr, ImageFormat.Jpeg);
    END;

Para redimensionar imágenes que se encuentran en la base de datos de la aplicación en un campo de tipo blob será necesario:

trigger OnAfterGetRecord()

            begin
                calcfields(Picture);
                picture.CreateinStream(instr);
                ServerFileName := '';

                //Generar el fichero en el servidor
                ServerFileName := FileMgt.InstreamExportToServerFile(InStr, 'jpeg');
                
                //Redimensionar imagen
                ResizeImage(ServerFileName, 800);

                //Volver a darle el valor al campo blob de la imagen redimensionada para mostrarlo en el layout
                clear(InStr);
                clear(OutStr);
                tempblob.CreateInStream(InStr);
                Picture.CreateOutStream(OutStr);
                copystream(outstr, instr);

                //Eliminar el fichero creado en el servidor
                FileMgt.DeleteServerFile(ServerFileName);

            end;

Por otro lado, el código en el caso de que las imágenes se encuentren en nuestro sistema de archivos requerirá de la importación del fichero en el servidor por lo que el cambio es mínimo con respecto a lo anterior pues cambian las dos primeras líneas:


trigger OnAfterGetRecord()

            begin
               
                Picture.Import('Z:\Imagen.jpg');
                picture.CreateinStream(instr);
                ServerFileName := '';

                //Generar el fichero en el servidor
                ServerFileName := FileMgt.InstreamExportToServerFile(InStr, 'jpeg');
                
                //Redimensionar imagen. 800 es el valor en pixeles de ancho a la que estoy redimensionándolo
                ResizeImage(ServerFileName, 800);

                //Volver a darle el valor al campo blob de la imagen redimensionada para mostrarlo en el layout
                clear(InStr);
                clear(OutStr);
                tempblob.CreateInStream(InStr);
                Picture.CreateOutStream(OutStr);
                copystream(outstr, instr);

                //Eliminar el fichero creado en el servidor
                FileMgt.DeleteServerFile(ServerFileName);

            end;

El resultado de utilizar este método se traduce en una pérdida de la calidad de la imagen (cuya incidencia es nula) pero una ganancia en el peso del archivo muy grande.

Como muestra el siguiente ejemplo de los dos ficheros generados con y sin este método:

Y a nivel de calidad, al hacer zoom:

Pero, sin zoom, la diferencia es inapreciable y estamos tomando como ejemplo una imágen mostrada a tamaño de un folio entero y artística:

El tamaño se ha reducido un 98% y, evidentemente, ha conllevado una perdida de calidad importante. En casos como imágenes de productos en las que se muestran en el informe a un tamaño menor, la pérdida de calidad sería imperceptible. No óbice, en caso de querer mayor calidad la redimensión puede realizarse con otro valor de pixeles de ancho de forma que el peso del archivo aumentará del mismo modo que la calidad.

Para aumentar la eficiencia de un report que muestre imágenes cuyo tamaño, a priori, nos es desconocido, podría establecerse que la llamada a la funcion se realice en funcion del tamaño del archivo con la funcion «Length» en lugar de realizarse siempre.

De esta forma, se acabó el decir siempre «es que las imágenes deben ser mas pequeñas y para hacerlo la abrís con paint, redimensionáis, y la guardáis en otro formato». Esto es viable para el logo de la empresa pero no para una marabunta de imágenes que puedan estar adscritas a diversas figuras que contiene la aplicación o imágenes externas.