RadImageEditor load and save image from SQL database

1 Answer 103 Views
ImageEditor
Mark
Top achievements
Rank 1
Mark asked on 20 Oct 2021, 03:29 AM

I am using the simple code below to load and image from SQL, then save it back to the Image datatype column.

I am able to load the image, but when I perform ANY edit feature (from the RadImageEditor control) the size of the image (relative size on disk in bytes) increases over 10x the original image.  Eventually it will consume all the memory and crash the application.  This behavior does not happen if I load a fixed image directly from disk.

The two attached images show the size of the image after immediately loading it, then the size of the image after performing a single Rotate 90 degrees.  

I cannot find any example of either loading an image from a DB nor saving to DB. Please review and provide any correction to loading and saving images (blob, image)  from SQL, or please provide a simple example of loading and saving a SQL image.

FYI: objAttach.ImageBlob is defined as Byte()

    Private Sub frmAttachmentViewer_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        Me.lblMessage.Text = ""
        mbImageChanged = False
        Dim ms As MemoryStream = New MemoryStream(objAttach.ImageBlob, 0, objAttach.ImageBlob.Length)
        Dim img As Image = Image.FromStream(ms)
        Me.RadImageEditor1.OpenImage(img)
        lblImgSize.Text = "Size: " & ms.Length & " KB"
    End Sub

    Private Sub RadImageEditor1_CurrentImageChanged(sender As Object, e As ImageChangedEventArgs) Handles RadImageEditor1.CurrentImageChanged
        Dim ms As New MemoryStream()
        Me.RadImageEditor1.SaveImage(ms)
        lblImgSize.Text = "Size: " & ms.Length & " KB"
    End Sub

    Private Sub cmdSave_Click(sender As Object, e As EventArgs) Handles cmdSave.Click
        Dim ms As New MemoryStream()
        Try

            Me.RadImageEditor1.SaveImage(ms)

            Dim iToInt As Integer = Convert.ToInt32(ms.Length)
            Dim imgBinaryData As Byte() = New Byte(iToInt - 1) {}
            imgBinaryData = ms.ToArray()
            objAttach.ImageBlob = imgBinaryData

            objAttach.save()
            Me.lblMessage.Text = "Image Saved"
        Catch ex As Exception
            MessageBox.Show("Error Saving Image: " & vbCrLf & vbCrLf & ex.Message)
        Finally
            ms = Nothing
        End Try
    End Sub

 

1 Answer, 1 is accepted

Sort by
0
Todor Vyagov
Telerik team
answered on 20 Oct 2021, 08:49 AM

Hello Mark,

The size of the image(byte array) depends on the specified ImageFormat when the image is saved. By default, ImageFormat.Bmp is used in the RadImageEditor. I assume that on your database the image format is more compressed. This is why there is a big difference in the size of the byte array. In order to see the image format of the loaded image, you can use its RawFormat property and follow the approach used here: Find image format using Bitmap object

If you explicitly specify the same image format in the SaveImage method there will be no significant difference in the memory when the image is modified.

Me.RadImageEditor1.SaveImage(ms, System.Drawing.Imaging.ImageFormat.Png)

I hope this information helps. If you need any further assistance please don't hesitate to contact me

Regards,
Todor Vyagov
Progress Telerik

Virtual Classroom, the free self-paced technical training that gets you up to speed with Telerik and Kendo UI products quickly just got a fresh new look + new and improved content including a brand new Blazor course! Check it out at https://learn.telerik.com/.

Mark
Top achievements
Rank 1
commented on 20 Oct 2021, 11:38 PM

This did help to identify the file type (Jpeg, in this case).  however, now when I check it's size after edit it is 25% of the original size (instead of the 10x increase in size if I don't specify an imageformat on the SaveImage method).  After editing (single edit of rotate 90 degrees), performing a SaveImage(ms,ImageFormat.Jpeg), then saving back to DB and reopening image in control I start having the same bad behavior with the RadImageEditor and it will eventually crash.  The size of the original Jpeg retrieved from the database matches the original size of the jpeg on disk that was uploaded.  Whatever is happening, it is in the RadImageEditor.  Since I cannot tell the control what type of image it is during the OpenImage method, it has to be making some assumption on what type of image it is.  Once loaded in the control, all manipulation of the image is happening in the "black box" code of your control.  I'm not sure if there is a better method to check the size of the image after each operation, but the only method I have found is to check the CurrentImageChanged event and do the SaveImage(ms) so I can check the size of the ms object.  It doesn't appear there is any property of the RadImageEditor that indicates edited image size.  Is there any issues I might be creating by performing the SaveImage method after every operation?  Also, is there a better method to loading an image from the database than this:

        Dim ms As MemoryStream = New MemoryStream(objAttach.ImageBlob)
        Dim img As System.Drawing.Image = Image.FromStream(ms)
        Me.RadImageEditor1.OpenImage(img)

Performing the image format function you suggested on img will tell me the image's format, but trying to do a SaveImage(ms,FoundImageFormat) still returns a size significantly different than the original image.

Again, once I re-open the saved image that was resized, it crashes the control.  For this, Is there a way to clear the buffer or reset this control before closing the form it is on?  Reopening the form and attempting load the image after an the initial error will crash the entire application.

Thanks again for your assistance.

Todor Vyagov
Telerik team
commented on 25 Oct 2021, 07:03 PM

Hi, Mark,

In order to investigate the precise case, based on your description I have created a sample database table that has an image column. I have added a single record. I have also created a VisualStudio project and implemented save and load operations to this single record.

On my side, I do not experience any crashes, only the described behavior with Jpeg images when loaded from the file system the memory stream is about 4 times larger than the same image loaded from the server. After many tests with Bitmap objects and debugging the source code, I found out that the size of the memory stream is changed because of the way the Bitmap is copied in the RadImageEditor control. RadImageEditor creates a new then opening a new image/new Bitmap(originalImage)/, instead of cloning the image /originalImage.Clone()/. This is necessary because the Clone() method keeps a reference to the original image and thus many kinds of exceptions may occur when the original or the cloned images are edited or disposed. A more detailed answer on this topic is available here: What's the difference between Bitmap.Clone() and new Bitmap(Bitmap)? 

If you want to prevent this size from changing this way, you can set the Currentimage of RadImageEditor right after the image is loaded. Here is an updated version of your code snippet:

Private Sub frmAttachmentViewer_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    Me.lblMessage.Text = ""
    mbImageChanged = False
    Dim ms As MemoryStream = New MemoryStream(objAttach.ImageBlob, 0, objAttach.ImageBlob.Length)
    Dim img As Image = Image.FromStream(ms)
    Me.radImageEditor1.OpenImage(img)
    Me.radImageEditor1.CurrentBitmap = img
    lblImgSize.Text = "Size: " & ms.Length & " KB"
End Sub

 

I hope this information is helpful.
Mark
Top achievements
Rank 1
commented on 28 Oct 2021, 01:50 AM

I'm not sure if adding the CurrentBitmap line corrects anything.   It produces the same results either with or without it.

the images I am dealing with are high resolution photos (6000KB+, mostly Jpeg).  They are loaded from disk into the db (Image data type).  They are not resampled or compressed upon loading them to the DB, and if they are written back to disk they appear the same size as the original that was initially loaded.

After some further testing I uploaded a smaller jpeg (120kb) to the DB.  It loaded to the RdadImageEditor the same size as from disk (120kb) but resampled to 20kb after the first rotate 90.   I also tried this with other image types from the DB (gif, bmp, png) and even though they were not large files, I did not get the drastic size changes after the first Rotate 90 on these as I got from the Jpegs.   After I save the smaller, edited jpeg back to the DB (now 20kb vs the original 120kb) and then reload it to RadImageEditor, it loads as 20KB and does not adjust drastically when performing the Rotate 90.  So it appears there is something with my original jpeg files that is resampled upon loading them to the control.

I'm not sure what happens within the control that would cause such a drastic resampling of a Jpeg compared to other image types. Also, I am curious if resampling the Jpeg to a much smaller size does anything to affect the image quality, should I need to write it back out to disk.   The smaller images were not causing the error as I experience with the larger images, but the change in size is a little concerning.  It appears there might be a memory leak somewhere with the larger images.

Description of the larger images that cause an error: After I perform the initial rotate, my image size drops to 25% of the original.  If I continue to hit the Rotate 90 button another 10 times it will eventually give me an "A generic error occured in GDI+", then the form will close.   If I attempt to reopen the form and load the same image, I get a "Parameter is not valid" on the OpenImage method and the entire application crashes.  

I really need to be able to edit the original image and will need to inquire with the users if the resampling of the size is an issue, because if the intent is to edit the image, they should expect a resize.  However, the crashing of the control when a large image is loaded is what is concerning and would prevent me from using this control.

Please try your code again using a large Jpeg and see if you notice anything I might be missing.  Below is my entire code from the form to make sure I am not doing something to cause this on my end.  I have also attached a sample large jpeg image that I have tested and produced the errors with.

Imports System.ComponentModel
Imports System.Drawing.Drawing2D
Imports System.IO
Imports CLIB_MAPPS
Imports Telerik.WinControls.UI

Public Class frmAttachmentViewer

    Dim objAttach As CLIB_MAPPS.Attachment
    Dim mbImageChanged As Boolean = False
    Dim mbIsLoading As Boolean = True
    Dim mImageFormat As System.Drawing.Imaging.ImageFormat
    Public Property objectAttach As CLIB_MAPPS.Attachment
        Get
            Return objAttach
        End Get
        Set(ByVal Value As CLIB_MAPPS.Attachment)
            objAttach = Value
        End Set
    End Property


    Private Sub frmAttachmentViewer_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

        ' --- adjust size of this form ** this does NOT make consideration of DPI (eg. 125%, 150%) **
        Me.Width = CInt(My.Computer.Screen.WorkingArea.Size.Width * 0.8)
        Me.Height = CInt(My.Computer.Screen.WorkingArea.Size.Height * 0.8)

        Me.lblMessage.Text = "Image Loaded"
        mbImageChanged = False
        Dim ms As MemoryStream = New MemoryStream(objAttach.ImageBlob, 0, objAttach.ImageBlob.Length)
        Dim img As System.Drawing.Image = Image.FromStream(ms)
        mImageFormat = GetImageFormat(img)
        Me.RadImageEditor1.OpenImage(img)
        'Me.RadImageEditor1.CurrentBitmap = img
        lblImgSize.Text = "Size: " & Math.Round(ms.Length / 1024, 1) & " KB"
        mbIsLoading = False
    End Sub

    Private Sub RadImageEditor1_ImageLoaded(sender As Object, e As AsyncCompletedEventArgs) Handles RadImageEditor1.ImageLoaded
        Me.RadImageEditor1.ImageEditorElement.ZoomElement.DropDownList.Text = "Auto"
        Dim factor As Single = Me.GetAutoZoomFactor()
        Me.RadImageEditor1.ImageEditorElement.ZoomFactor = New SizeF(factor, factor)
    End Sub

    Protected Overridable Function GetAutoZoomFactor() As Single
        Dim factorX As Single = (Me.RadImageEditor1.ImageEditorElement.Size.Width - Me.RadImageEditor1.ImageEditorElement.CommandsElementWidth) / CSng(System.Convert.ToSingle(Me.RadImageEditor1.ImageEditorElement.CurrentBitmap.Width))
        Dim factorY As Single = (Me.RadImageEditor1.ImageEditorElement.Size.Height - Me.RadImageEditor1.ImageEditorElement.ZoomElementHeight) / CSng(System.Convert.ToSingle(Me.RadImageEditor1.ImageEditorElement.CurrentBitmap.Height))
        Return Math.Min(factorX, factorY)
    End Function
    Public Shared Function GetImageFormat(ByVal img As System.Drawing.Image) As System.Drawing.Imaging.ImageFormat
        If img.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Jpeg) Then Return System.Drawing.Imaging.ImageFormat.Jpeg
        If img.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Bmp) Then Return System.Drawing.Imaging.ImageFormat.Bmp
        If img.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Png) Then Return System.Drawing.Imaging.ImageFormat.Png
        If img.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Emf) Then Return System.Drawing.Imaging.ImageFormat.Emf
        If img.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Exif) Then Return System.Drawing.Imaging.ImageFormat.Exif
        If img.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Gif) Then Return System.Drawing.Imaging.ImageFormat.Gif
        If img.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Icon) Then Return System.Drawing.Imaging.ImageFormat.Icon
        If img.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.MemoryBmp) Then Return System.Drawing.Imaging.ImageFormat.MemoryBmp

        If img.RawFormat.Equals(System.Drawing.Imaging.ImageFormat.Tiff) Then
            Return System.Drawing.Imaging.ImageFormat.Tiff
        Else
            Return System.Drawing.Imaging.ImageFormat.Wmf
        End If
    End Function
    Private Sub btnClose_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnClose.Click

        Me.Close()

    End Sub

    Private Sub RadImageEditor1_CurrentImageChanged(sender As Object, e As ImageChangedEventArgs) Handles RadImageEditor1.CurrentImageChanged
        If Not mbIsLoading Then
            Me.lblMessage.Text = "Image Edited"
            mbImageChanged = True

            Dim ms As New MemoryStream()
            Me.RadImageEditor1.SaveImage(ms, mImageFormat)
            lblImgSize.Text = "Size: " & Math.Round(ms.Length / 1024, 1) & " KB"
            Me.cmdSave.Visible = True
            Me.btnClose.Text = "Cancel"
            ms = Nothing
        End If
    End Sub

    Private Sub cmdSave_Click(sender As Object, e As EventArgs) Handles cmdSave.Click
        Dim ms As New MemoryStream()
        Try

            Me.RadImageEditor1.SaveImage(ms, mImageFormat)

            Dim iToInt As Integer = Convert.ToInt32(ms.Length)
            Dim imgBinaryData As Byte() = New Byte(iToInt - 1) {}
            imgBinaryData = ms.ToArray()
            objAttach.ImageBlob = imgBinaryData
            objAttach.ThumbnailBlob = Attachment.TryCreateThumbnail(imgBinaryData)

            objAttach.save()
            Me.lblMessage.Text = "Image Saved"
        Catch ex As Exception
            MessageBox.Show("Error Saving Image: " & vbCrLf & vbCrLf & ex.Message)
        Finally
            ms = Nothing
        End Try
    End Sub

    Private Sub frmAttachmentViewer_Closing(sender As Object, e As CancelEventArgs) Handles Me.Closing
        Me.RadImageEditor1 = Nothing
    End Sub
End Class

 

Todor Vyagov
Telerik team
commented on 01 Nov 2021, 03:26 PM

Thanks for the attached image. It helped me reproduce the problem you are facing.

The RadImageEditor control does not resample the image when it is being saved. It simply uses the appropriate API to clone the original image which causes the MemoryStream size to be changed when a JPEG image is used. To copy the image RadImageEditor uses the bitmap constructor new Bitmap(originalImage), which is the recommended way to clone a bitmap, as I noted in my previous post. In addition to this, I have also saved the modified image(with a much smaller size) and visually compared it to the original in a paint preview program and there does not seem to be image quality loss. Indeed when you zoom to a certain level 1000-1500% you can see that some pixels differ from the original, but this is a result of the compression optimization.

The exception after several rotate operations is caused by the increasing memory used for the undo/redo operations. You can reset this memory by opening the latest image again after one or several image edit operations:

Me.RadImageEditor1.OpenImage(Me.RadImageEditor1.CurrentBitmap)

 

Tags
ImageEditor
Asked by
Mark
Top achievements
Rank 1
Answers by
Todor Vyagov
Telerik team
Share this question
or