Resizing images and reducing the size of PDFs in Business Central OnPremise

0
133

When displaying images in a Business Central report, whether they are images loaded into a blob field type like product images or external images that we may have in our file system, the challenge of image size and quality arises.

Normally, for a long time, I have always intended for the client to be concerned about the weight of these files so that the report, when printed, would not take an infinite amount of time to render these images, and also, in the case of documents that are sent by email, to avoid the weight of the generated PDF file from being too large to allow for that send.

Recently I found a method, using dotNet, to modify the quality of the images and thus reduce the weight and render load of the reports. In this way, the client no longer has to worry about modifying the images that are imported into the application or those that are found in their file system (which could be received externally and have no viable way to modify them efficiently).

For all this, first the necessary variables must be declared:

dotnet
{
    assembly(System.Drawing)

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

As a note, prior to the declarations, the probing paths, which are the directories where the DLL’s we want to reference are located, must be declared. To do this, from Visual Studio Code:

File> Preferences> Configuration> Extensions> AL Language Extension> Assembly Probing Paths. (You can also search directly for this last item and it will appear)

We must add: C:/Windows/Assembly/

And also: C:\Program Files\Microsoft Dynamics 365 Business Central\180\Service\Add-ins

With the Probing Paths configured and the declarations made, you can now start with the code.
The function responsible for resizing an image file is as follows:

    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
        // Load image
        CLEAR(BitMapOriginal);
        CLEAR(BitmapResized);
        BitMapOriginal := BitMapOriginal.Bitmap(ServerFileName);
        
        // Calculate New Dimensions
        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;

        // Resize image
        BitmapResized := BitmapResized.Bitmap(BitMapOriginal, NewWidth, NewHeight);
        

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

To resize images that are in the application’s database in a blob field type, it will be necessary:

trigger OnAfterGetRecord()

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

                //Upload file to server
                ServerFileName := FileMgt.InstreamExportToServerFile(InStr, 'jpeg');
                
                //Resize image
                ResizeImage(ServerFileName, 800);

                //Reassign blob field with resized image in order to show in layout
                clear(InStr);
                clear(OutStr);
                tempblob.CreateInStream(InStr);
                Picture.CreateOutStream(OutStr);
                copystream(outstr, instr);

                //Erase file on server
                FileMgt.DeleteServerFile(ServerFileName);

            end;

On the other hand, the code in the case that the images are in our file system will require the file to be imported to the server, so the change is minimal compared to the previous one as the first two lines change:


trigger OnAfterGetRecord()

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

                //Upload File on server
                ServerFileName := FileMgt.InstreamExportToServerFile(InStr, 'jpeg');
                
                //Resize Image. 800 is the pixel width value
                ResizeImage(ServerFileName, 800);

                //Reassign blob field with resized image in order to show in layout
                clear(InStr);
                clear(OutStr);
                tempblob.CreateInStream(InStr);
                Picture.CreateOutStream(OutStr);
                copystream(outstr, instr);

                //Erase file on server
                FileMgt.DeleteServerFile(ServerFileName);

            end;

The result of using this method results in a loss of image quality (whose incidence is nil) but a great gain in file weight.

As shown in the following example of the two files generated with and without this method:

And in terms of quality, when zooming in:

But without zoom, the difference is almost imperceptible and we are taking as an example an image shown at the size of an entire sheet and artistic:

The size has been reduced by 98% and, obviously, has resulted in a significant loss of quality. In cases such as product images that are shown in the report at a smaller size, the loss of quality would be imperceptible. However, in case you want higher quality, the resizing can be done with another width pixel value so that the file weight will increase in the same way as the quality.

To increase the efficiency of a report that displays images whose size is unknown beforehand, it could be established that the function call is performed based on the file size with the “Length” function instead of always being performed.

In this way, the saying “the images must be smaller and to do this, you open it with paint, resize it, and save it in another format” ends. This is viable for the company logo but not for a swarm of images that may be associated with various figures contained in the application or external images.