<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Craig Melville</title>
    <description>The latest articles on DEV Community by Craig Melville (@acekreations).</description>
    <link>https://dev.to/acekreations</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F508025%2F9dfb3d7a-20c9-4830-9f55-66d6fbd027ce.jpeg</url>
      <title>DEV Community: Craig Melville</title>
      <link>https://dev.to/acekreations</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/acekreations"/>
    <language>en</language>
    <item>
      <title>Processing Images with Web Assembly using wasm-vips</title>
      <dc:creator>Craig Melville</dc:creator>
      <pubDate>Wed, 21 Feb 2024 15:34:00 +0000</pubDate>
      <link>https://dev.to/acekreations/processing-images-with-web-assembly-using-wasm-vips-3oi2</link>
      <guid>https://dev.to/acekreations/processing-images-with-web-assembly-using-wasm-vips-3oi2</guid>
      <description>&lt;h2&gt;
  
  
  Backstory
&lt;/h2&gt;

&lt;p&gt;For the last few years I have run a site called &lt;a href="https://tiny.photos"&gt;tiny.photos&lt;/a&gt;, I built it because I couldn't find a tool that worked the way I wanted, and it was great! Except it was costing a lot of money. As you might expect, processing images is a bit resource intensive. So I set out on a quest to reduce costs, web assembly had always been in the back of my mind as a cool option but I wasn't sure if it would actually work. I figured I would play around with it and see what I could come up with. After a few long days of banging my head against the wall I got it working! In an effort to help other people along I thought I would do this little write up that will hopefully save some people the headache of figuring out &lt;a href="https://github.com/kleisauke/wasm-vips"&gt;wasm-vips&lt;/a&gt; and help others discover the power of in browser image processing.&lt;/p&gt;

&lt;h2&gt;
  
  
  wasm-vips
&lt;/h2&gt;

&lt;p&gt;If you do some googling on this subject you might run across a library called wasm-vips, which is exactly what it sounds like, a web assembly wrapper for &lt;a href="https://www.libvips.org/API/current/"&gt;libvips&lt;/a&gt;. Libvips is a fantastic C library for processing images so naturally wasm-vips should be a great option for processing images in the browser, right? Yes and no. Yes, wasm-vips is a fantastic option but it comes with the caveat that it is in early development and has &lt;em&gt;very&lt;/em&gt; minimal documentation. So having spent several days tinkering and sifting through the wasm-vips repo I'm sharing what I learned that was not available in the docs by creating a simple image resizing/cropping script.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting an image from the end user to wasm-vips
&lt;/h2&gt;

&lt;p&gt;Surprisingly, this is not as trivial as you would think given that you can't process an image if you don't have an image. &lt;/p&gt;

&lt;p&gt;Let's start with the basics, getting the files from a form.&lt;/p&gt;

&lt;p&gt;A basic file input&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"fileElem"&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"file"&lt;/span&gt; &lt;span class="na"&gt;multiple&lt;/span&gt; &lt;span class="na"&gt;accept=&lt;/span&gt;&lt;span class="s"&gt;"image/*"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Retrieve the files from the input&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// In my case I have additional logic to handle&lt;/span&gt;
&lt;span class="c1"&gt;// drag and drop functionality but this could be handled with &lt;/span&gt;
&lt;span class="c1"&gt;// an event listener or any other logic that you need&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleDrop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;dt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataTransfer&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;
    &lt;span class="nf"&gt;handleFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;([...&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
        &lt;span class="nf"&gt;processImages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we get to the good stuff, the &lt;code&gt;processImages()&lt;/code&gt; function. This is where everything wasm-vips related happens.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processImages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// make sure a file was actually passed in&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Create a new FileReader.&lt;/span&gt;
    &lt;span class="c1"&gt;// This is what gets us our image data&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;FileReader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// This fires once the image has been loaded&lt;/span&gt;
    &lt;span class="c1"&gt;// as an ArrayBuffer&lt;/span&gt;
    &lt;span class="nx"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// This is where the wasm-vips code will go&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// This initiates reading the file.&lt;/span&gt;
    &lt;span class="c1"&gt;// Note that we are using readAsArrayBuffer&lt;/span&gt;
    &lt;span class="c1"&gt;// this makes an ArrayBuffer that we can &lt;/span&gt;
    &lt;span class="c1"&gt;// then give to wasm-vips. There are other&lt;/span&gt;
    &lt;span class="c1"&gt;// options but this seems to work the best in my testing.&lt;/span&gt;
    &lt;span class="nx"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readAsArrayBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now inside our &lt;code&gt;fr.onload&lt;/code&gt; event we will have &lt;code&gt;event.target.result&lt;/code&gt;, this is our image in the form of an ArrayBuffer.&lt;/p&gt;

&lt;p&gt;Note: going forward we will be working with wasm-vips, I'm not including some of the basic setup stuff here that is available in the documentation, just the stuff that is not clearly documented anywhere.&lt;/p&gt;

&lt;p&gt;So now that we have our image data we want to process it with wasm-vips, right? Well it's actually not that hard, what we need to use is &lt;code&gt;vips.Image.thumbnailBuffer()&lt;/code&gt;. This function takes our &lt;code&gt;event.target.result&lt;/code&gt; as the file, a width and some settings.&lt;br&gt;
It looks something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;im&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vips&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;thumbnailBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;no_rotate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;crop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;crop&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The file and width are required, the settings are optional. &lt;/p&gt;

&lt;h2&gt;
  
  
  Processing images with wasm-vips
&lt;/h2&gt;

&lt;p&gt;Now that you hopefully have an idea of how this works let's look at some examples of processing images.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resize an image
&lt;/h3&gt;

&lt;p&gt;Say we want to resize an image within certain dimensions(1000px x 1000px) but not crop any of the image out.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;im&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vips&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;thumbnailBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;no_rotate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;crop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;vips&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Interesting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;none&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;vips.Interesting.none&lt;/code&gt; on the crop setting tells wasm-vips not to remove any part of the image.&lt;/p&gt;

&lt;p&gt;Also note where we have set the values for width and height. This ensures our image will be within these dimensions, if you removed the height setting then the image would only be constrained by the width. It's important to remember that width is required.&lt;/p&gt;

&lt;h3&gt;
  
  
  Crop an image
&lt;/h3&gt;

&lt;p&gt;Now if we want to crop the image we simply change the crop setting to &lt;code&gt;vips.Interesting.attention&lt;/code&gt;. This has the added benefit of applying a "smart cropping" that attempts to focus the crop on the most interesting part of the image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;im&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;vips&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;thumbnailBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;no_rotate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;crop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;vips&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Interesting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attention&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: if you have ever used libvips or one of it's associated libraries you are probably familiar with its tendency to rotate images unexpectedly(there are reasons, they are just beyond this post) so I always include the &lt;code&gt;no_rotate: true&lt;/code&gt; setting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Converting and Compressing
&lt;/h2&gt;

&lt;p&gt;The final step we need to take with wasm-vips is outputting our image, this also happens to be where you can convert and compress the image. For this post I am only going to cover saving a jpeg, as png compression is much more complicated and probably needs a post of it's own. Let's dive right into that code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;outBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;im&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;jpegsaveBuffer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not that scary right? A couple notes about this line, if you remember from up-top we are storing our image in the &lt;code&gt;im&lt;/code&gt; variable, so that is how we are actually getting the new image and saving it. And our Q setting is the quality of the image a.k.a. the compression. It's on a scale of 1-100, 100 being the highest quality and least compression.&lt;/p&gt;

&lt;h3&gt;
  
  
  saveBuffer functions
&lt;/h3&gt;

&lt;p&gt;The saveBuffer functions are how you save different file types, if we wanted to save a png it would be &lt;code&gt;pngsaveBuffer()&lt;/code&gt;, webp would be &lt;code&gt;webpsaveBuffer()&lt;/code&gt;. There are a whole bunch of the saveBuffer functions available. You can run these functions without passing any settings as well and they generally default to reasonable settings, however it's worth pointing out that different file formats have different settings and sometimes they can be difficult to decipher.&lt;/p&gt;

&lt;h2&gt;
  
  
  Saving an image
&lt;/h2&gt;

&lt;p&gt;At this point we have our processed image but we need to do something with it. In a lot of cases I'm sure the image will need to be uploaded to a server or displayed on screen but for &lt;a href="https://tiny.photos"&gt;tiny.photos&lt;/a&gt; I send the image right back to the user, so let me show you how I do that.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// using our outBuffer variable from the last step&lt;/span&gt;
&lt;span class="c1"&gt;// to create a blob&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;outBuffer&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`image/jpeg`&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;blobURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// here we create a hidden button that links to the blob&lt;/span&gt;
&lt;span class="c1"&gt;// and automatically clicks it with JS&lt;/span&gt;
&lt;span class="c1"&gt;// and the download is initiated on the client&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;downloadLink&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;downloadLink&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;blobURL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// this is where you can name the file&lt;/span&gt;
&lt;span class="c1"&gt;// make sure you have the correct extension&lt;/span&gt;
&lt;span class="nx"&gt;downloadLink&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;download&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`file-name.jpeg`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;downloadLink&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;downloadLink&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;downloadLink&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And there you have it, an image entirely processed with web assembly on the client, no server required!&lt;/p&gt;

&lt;p&gt;If you are diving head first into wasm-vips and had the same troubles I had learning a library that doesn't have any documentation I would recommend looking at the &lt;a href="https://www.libvips.org/API/current/"&gt;libvips documentation&lt;/a&gt;, finding what you want to use and then searching the &lt;a href="https://github.com/kleisauke/wasm-vips"&gt;wasm-vips repo&lt;/a&gt; for functions of the same or &lt;em&gt;similar&lt;/em&gt; names. It's not easy but it gets the job done. Also when you match functions you can use libvips as a reference for what arguments the function takes, I haven't found any case where they didn't work the exact same.&lt;/p&gt;

&lt;p&gt;If you would like to see wasm-vips in action please checkout &lt;a href="https://tiny.photos"&gt;tiny.photos&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>learning</category>
      <category>frontend</category>
      <category>serverless</category>
    </item>
  </channel>
</rss>
