-- Leo's gemini proxy

-- Connecting to g.mikf.pl:1965...

-- Connected

-- Sending request

-- Meta line: 20 text/gemini

Writing a JPEG compressor GUI in .NET Visual Basic quickly

2022-12-13

Used a template for creating WinForms application.

Added a ComboBox in top left corner, set its anchoring to Top Left in Properties

Added a StatusStrip and a single StatusLabel to it, on the bottom of the window

Added a NumericUpDown so that it aligned to StatusStrip and left edge, anchoring Bottom Left

Added a TrackBar, set its orientation to Vertical, aligned it to ComboBox and NumericUpDown, set its anchoring to Top Bottom Left

took TrackBar width down to 101 (minimum), then aligned the widths of ComboBox and NumericUpDown to it

Added a PictureBox, aligned it with TrackBar to the left, top of the window from the top, window edge on the right, and StatusStrip on the bottom, set anchoring to Top Bottom Left Right

Set TrackBar's Maximum to 100. Minimum was already set to 0, and so was Maximum set to 100 in case of NumericUpDown.

Set ComboBox Collection Property to contain just one element, "JPG"

I switched the Properties pane to the events view and clicked twice on ValueChanged for both NumericUpDown and TrackBar to have them autoassigned

Private valueChangeSource As Object = Nothing

Private Sub TrackBar1_ValueChanged(sender As Object, e As EventArgs) Handles TrackBar1.ValueChanged
    If valueChangeSource Is Nothing Then
        valueChangeSource = TrackBar1
    ElseIf valueChangeSource Is TrackBar1 Then
        valueChangeSource = Nothing
        Return
    End If
    NumericUpDown1.Value = TrackBar1.Value
End Sub

Private Sub NumericUpDown1_ValueChanged(sender As Object, e As EventArgs) Handles NumericUpDown1.ValueChanged
    If valueChangeSource Is Nothing Then
        valueChangeSource = NumericUpDown1
    ElseIf valueChangeSource Is NumericUpDown1 Then
        valueChangeSource = Nothing
        Return
    End If
    TrackBar1.Value = NumericUpDown1.Value
End Sub

`Nothing` is actually a value of Nullable, don't do this.

I could also probably use the `sender` parameter to deduplicate this code more or less neatly.

This is just the code to keep them in sync.


Added an OpenFileDialog. Autoassigned a new handler for its FileOk event

Dim original As Image = New System.Drawing.Bitmap(100, 100)
Private Sub OpenFileDialog1_FileOk(sender As OpenFileDialog, e As System.ComponentModel.CancelEventArgs) Handles OpenFileDialog1.FileOk
    original = Image.FromFile(sender.FileName)
End Sub

Yes it creates a dummy bitmap first, that maybe is not needed but is neat.


It now turns out that the Image.Save method, to accept EncoderParameters so that we can pick the quality instead of the default, requires us to not use ImageFormat but ImageCodecInfo. Help came from the documentation example of EncoderParameter class usage, specifically the GetEncoder function from there, although I wrote it out differently:

Function Codec() As Imaging.ImageCodecInfo
    'If ComboBox1.SelectedValue = "" Then
    '    ComboBox1.SelectedValue = "JPG"
    'End If
    'If Not ComboBox1.SelectedValue = "JPG" Then
    '    Throw New ArgumentException
    'End If
    Dim format As ImageFormat = ImageFormat.Jpeg
    Dim codecs = Imaging.ImageCodecInfo.GetImageEncoders
    Dim result = (From approached In codecs
                    Select approached
                    Where approached.FormatID = format.Guid
                        ).First
    Return result
End Function

This is supposed to handle the ComboBox selection. It's so far non-functional. Would be for picking other lossy formats someday. Such as `ImageFormat.Webp` or `ImageFormat.Heif`.

If you want, remove the ComboBox and resize&anchor the TrackBar to the top of the window.


The other parameter to our `Image.Save` will be EncoderParameters:

Function Params() As EncoderParameters
    Dim obj = New EncoderParameters(1)
    Dim quality As Long = NumericUpDown1.Value
    Dim param = New EncoderParameter(
        Encoder.Quality, quality)
    obj.Param(0) = param
    Return obj
End Function

Now what's left is to generate and preview the compressed image:

Dim compressed As Byte()

Sub Render()
    Dim stream = New MemoryStream
    original.Save(stream, Codec, Params)
    compressed = stream.ToArray
    Dim img = Image.FromStream(stream)
    PictureBox1.Image = img
    stream.Dispose()
End Sub

And add a call to `Render()` at the end of the both ValueChanged and FileOk handlers.

The `compressed` field is so far unused but this is our original compression output, as lossy compression could be non-deterministic and come out differently each time. We are to be saving it to file to achieve full functionality.


And for the last thing, we need our application to start with asking for a file:

Select Form1 in the properties editor, switch view to events, and double click the Load event to autoassign a handler for it.

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    OpenFileDialog1.ShowDialog()
End Sub

I really hope I hadn't omitted anything. Many things need yet to be done, including drag move around, scaling. PictureBox image ("client controllers") can be drag-moved by making handlers to set a variable on MouseUp and MouseDown, and a MouseMove handler to add `e.x` to PictureBox.Left and so on. Scaling could be handled with MouseWheel.


Saving the byte array to a file will be trivial, can be triggered by a click to the image, or by handling window close event (and making the closing procedure be to cancel the file save dialog, for example), or by a keyboard shortcut.


Many things need to be polished, like the default filenames in file chooser, filetype filters in the chooser... But we wanted a thing quick to hack and we got it. Many optimization can be made but are not needed. TabStop of TrackBar could be set to one or a different value, but not necessarily, as it reduces the wastefulness of resources. Asynchronicity could be introduced, especially considering large images, would probably be needed for responsiveness.


And one of the most needed features that I only now remembered from the Android app JPEG Optimizer (com.mixaimaging.jpeg.optimizerfree) would be to have resizing of images as part of the compressing. Having live preview of a selection of downscaling algorithms would be so so cool.


Oh, and yeah, the StatusStrip is to be for showing the file sizes and original resolution.


EOF

-- Response ended

-- Page fetched on Tue May 21 19:03:36 2024