Created
November 3, 2026 16:54
-
-
Save ericlaw1979/95f8739e1de5479ce6d05045084fff80 to your computer and use it in GitHub Desktop.
JPEGify for Clearinet.app
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using Clearinet; | |
| using System; | |
| using System.Drawing; | |
| using System.Drawing.Imaging; | |
| using System.IO; | |
| using System.Windows.Forms; | |
| [assembly: Clearinet.RequiredVersion("0.0.3.0")] | |
| namespace MakeJPEG | |
| { | |
| public class JPEGify : IAutoTamper | |
| { | |
| private bool _autoConvert; | |
| private long _autoJPEGQuality = 80; | |
| private readonly ToolStripMenuItem _rulesMenuItem = new ToolStripMenuItem("JPEGify I&mages..."); | |
| private readonly ToolStripMenuItem _contextMenuItem = new ToolStripMenuItem("&JPEGify..."); | |
| public void OnPeekAtRequestHeaders(Exchange exchange) { } | |
| public void AutoTamperRequestBefore(Exchange exchange) { } | |
| public void AutoTamperRequestAfter(Exchange exchange) { } | |
| public void OnPeekAtResponseHeaders(Exchange exchange) | |
| { | |
| if (_autoConvert && exchange?.ResponseHeaders != null && exchange.ResponseHeaders.ExistsAndContains("Content-Type", "image/")) | |
| { | |
| // TODO: Enable response buffering. | |
| // Clearinet handles the actual response tampering in AutoTamperResponseBefore. | |
| } | |
| } | |
| public void AutoTamperResponseBefore(Exchange exchange) | |
| { | |
| if (!_autoConvert) return; | |
| ProcessExchange(exchange, _autoJPEGQuality); | |
| } | |
| public void AutoTamperResponseAfter(Exchange exchange) { } | |
| public void OnBeforeReturningGeneratedError(Exchange exchange) { } | |
| private void ProcessExchange(Exchange exchange, long quality) | |
| { | |
| if (exchange == null || !exchange.bHasResponse || exchange.responseCode != 200) | |
| { | |
| return; | |
| } | |
| if (!exchange.ResponseBody.HasData() | |
| || exchange.ResponseHeaders == null | |
| || !exchange.MediaType.OICStartsWith("image/")) | |
| { | |
| return; | |
| } | |
| if (!exchange.MediaTypeIs("image/jpeg") && | |
| !exchange.MediaTypeIs("image/webp") && | |
| !exchange.MediaTypeIs("image/png") && | |
| !exchange.MediaTypeIs("image/gif") && | |
| !exchange.MediaTypeIs("image/jxl") && | |
| !exchange.MediaTypeIs("image/bmp") && | |
| !exchange.MediaType.OICStartsWith("image/jp")) | |
| { | |
| return; | |
| } | |
| try | |
| { | |
| int oldSize = exchange.ResponseBody?.Length ?? 0; | |
| MakeJPEGOfQuality(exchange, quality); | |
| exchange.oFlags["Size-Before-JPEGify"] = oldSize.ToString("N0"); | |
| float percent = oldSize == 0 ? 0F : 100F * (exchange.ResponseBody.Length / (float)oldSize); | |
| string summary = $"JPEGify to {exchange.ResponseBody.Length:N0} from {oldSize:N0} bytes ({percent:N1}%)"; | |
| CApp.UI.SetStatusText(summary); | |
| } | |
| catch (Exception ex) | |
| { | |
| CApp.Log.Log(ex.ToString()); | |
| } | |
| } | |
| private void MakeJPEGOfQuality(Exchange exchange, long quality) | |
| { | |
| if (quality < 1 || quality > 100) | |
| { | |
| throw new ArgumentOutOfRangeException(nameof(quality), quality, "Quality must be between 1 and 100"); | |
| } | |
| if (!exchange.ResponseBody.HasData()) | |
| { | |
| return; | |
| } | |
| byte[] arrBody = exchange.ResponseBody; | |
| if (exchange.MediaTypeIs("image/webp")) | |
| { | |
| arrBody = Clearinet.Utilities.ConvertWebPToPNG(arrBody); | |
| } | |
| else if (exchange.MediaTypeIs("image/jxl")) | |
| { | |
| arrBody = Clearinet.Utilities.ConvertJXLToJPEG(arrBody); | |
| } | |
| using (MemoryStream inputStream = new MemoryStream(arrBody)) | |
| using (Bitmap bitmap = new Bitmap(inputStream)) | |
| using (MemoryStream outputStream = new MemoryStream()) | |
| { | |
| ImageCodecInfo encoder = GetEncoder(ImageFormat.Jpeg) ?? throw new InvalidOperationException("Unable to find a JPEG encoder."); | |
| EncoderParameters parameters = new EncoderParameters(1); | |
| parameters.Param[0] = new EncoderParameter(Encoder.Quality, quality); | |
| bitmap.Save(outputStream, encoder, parameters); | |
| exchange.ResponseBody = outputStream.ToArray(); | |
| exchange.ResponseHeaders["Content-Type"] = "image/jpeg"; | |
| exchange.ResponseHeaders["Cache-Control"] = "no-cache"; | |
| } | |
| } | |
| private static ImageCodecInfo GetEncoder(ImageFormat format) | |
| { | |
| ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders(); | |
| foreach (ImageCodecInfo codec in codecs) | |
| { | |
| if (codec.FormatID == format.Guid) | |
| { | |
| return codec; | |
| } | |
| } | |
| return null; | |
| } | |
| public void OnBeforeUnload() | |
| { | |
| _autoConvert = false; | |
| _rulesMenuItem.GetCurrentParent()?.Items.Remove(_rulesMenuItem); | |
| _contextMenuItem.GetCurrentParent()?.Items.Remove(_contextMenuItem); | |
| _rulesMenuItem.Dispose(); | |
| _contextMenuItem.Dispose(); | |
| } | |
| public void OnLoad() | |
| { | |
| CApp.UI.AddExtensionRulesMenuItem(_rulesMenuItem); | |
| CApp.UI.lvExchanges.AddExtensionMenuItem(_contextMenuItem); | |
| _rulesMenuItem.Click += (s, e) => | |
| { | |
| bool newValue = !_rulesMenuItem.Checked; | |
| if (newValue) | |
| { | |
| string qualityText = frmPrompt.GetUserString( | |
| "Set JPEG Quality", | |
| "Enter a value, 1 to 100", | |
| _autoJPEGQuality.ToString(), | |
| true, | |
| frmPrompt.PromptKind.Numbers); | |
| if (!qualityText.HasText()) | |
| { | |
| newValue = false; | |
| } | |
| else | |
| { | |
| if (!long.TryParse(qualityText, out long parsedQuality) || parsedQuality < 1 || parsedQuality > 100) | |
| { | |
| newValue = false; | |
| } | |
| else | |
| { | |
| _autoJPEGQuality = parsedQuality; | |
| } | |
| } | |
| } | |
| _rulesMenuItem.Checked = _autoConvert = newValue; | |
| }; | |
| _contextMenuItem.Click += (s, e) => | |
| { | |
| string qualityText = frmPrompt.GetUserString( | |
| "Set JPEG Quality", | |
| "Enter a value, 1 to 100", | |
| _autoJPEGQuality.ToString(), | |
| true, | |
| frmPrompt.PromptKind.Numbers); | |
| if (!qualityText.HasText()) return; | |
| if (!long.TryParse(qualityText, out long quality) || quality < 1 || quality > 100) return; | |
| Exchange[] selectedExchanges = CApp.UI.GetSelectedExchanges(); | |
| foreach (Exchange exchange in selectedExchanges) | |
| { | |
| ProcessExchange(exchange, quality); | |
| } | |
| CApp.UI.actRefreshUI(false); | |
| CApp.UI.SetStatusText("JPEGify Complete"); | |
| }; | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment