💾 Archived View for g.mikf.pl › gemlog › 2022-12-13-winforms-jpeg.gmi captured on 2023-11-14 at 07:43:26. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2023-01-29)
-=-=-=-=-=-=-
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.
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:
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