<?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: Masaki Komagata</title>
    <description>The latest articles on DEV Community by Masaki Komagata (@komagata).</description>
    <link>https://dev.to/komagata</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%2F1244422%2F8e5eef2f-19d7-447a-950e-5d3f631266ba.jpeg</url>
      <title>DEV Community: Masaki Komagata</title>
      <link>https://dev.to/komagata</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/komagata"/>
    <language>en</language>
    <item>
      <title>Active Storage without libvips or ImageMagick: a pure-Ruby alternative</title>
      <dc:creator>Masaki Komagata</dc:creator>
      <pubDate>Tue, 21 Apr 2026 12:58:55 +0000</pubDate>
      <link>https://dev.to/komagata/active-storage-without-libvips-or-imagemagick-a-pure-ruby-alternative-4o99</link>
      <guid>https://dev.to/komagata/active-storage-without-libvips-or-imagemagick-a-pure-ruby-alternative-4o99</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Update (2026-04-23):&lt;/strong&gt; v0.3.0 dropped the last remaining gem dependency (&lt;code&gt;image_processing&lt;/code&gt;) — the library is now fully zero-dependency. There's also a &lt;a href="https://komagata.github.io/pura-image/" rel="noopener noreferrer"&gt;&lt;strong&gt;browser playground&lt;/strong&gt;&lt;/a&gt; where you can run &lt;code&gt;ImageProcessing::Pura&lt;/code&gt; chains directly in your browser via ruby.wasm.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;I built &lt;a href="https://github.com/komagata/pura-image" rel="noopener noreferrer"&gt;&lt;code&gt;pura-image&lt;/code&gt;&lt;/a&gt; — a pure-Ruby image processing library that drops into Rails Active Storage as a replacement for &lt;code&gt;ImageProcessing::Vips&lt;/code&gt; / &lt;code&gt;ImageProcessing::MiniMagick&lt;/code&gt;. &lt;strong&gt;No &lt;code&gt;brew install vips&lt;/code&gt;. No &lt;code&gt;apt install imagemagick&lt;/code&gt;. No C compiler. Just &lt;code&gt;gem install&lt;/code&gt;.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;# Gemfile
  gem "image_processing"
&lt;span class="gi"&gt;+ gem "pura-image"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/application.rb&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;active_storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;variant_processor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:pura&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;# Dockerfile
&lt;span class="gd"&gt;- RUN apt-get install -y libvips-dev imagemagick
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the entire integration. Models and views stay exactly the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it in your browser
&lt;/h2&gt;

&lt;p&gt;If you'd rather see it run before reading further:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://komagata.github.io/pura-image/" rel="noopener noreferrer"&gt;komagata.github.io/pura-image&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The page boots ruby.wasm and runs &lt;code&gt;ImageProcessing::Pura&lt;/code&gt; chains on a generated 400×300 image (or one you drop in). The exact code shown is the same code you'd put in a Rails Active Storage variant definition — but to be precise, only the &lt;code&gt;ImageProcessing::Pura&lt;/code&gt; chain runs in the browser, not Active Storage itself (no blob storage, no orchestration). Still, if you've ever wondered whether your Rails variant code can survive in a wasm sandbox: yes, it can.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built this
&lt;/h2&gt;

&lt;p&gt;I teach Rails to beginners, and the single most common stumbling block isn't Ruby syntax or Rails conventions — it's the system-library install dance for Active Storage's image processing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;macOS: &lt;code&gt;brew install vips&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Ubuntu/Debian: &lt;code&gt;apt install libvips-dev&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Windows: …good luck&lt;/li&gt;
&lt;li&gt;Docker: another layer in your &lt;code&gt;RUN apt-get install&lt;/code&gt; chain&lt;/li&gt;
&lt;li&gt;CI: another minute on every build&lt;/li&gt;
&lt;li&gt;Heroku/Fly.io/Render: hope your buildpack covers it&lt;/li&gt;
&lt;li&gt;ruby.wasm: not happening&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every one of those is a wall a learner can hit. Some of them just give up before writing their first &lt;code&gt;has_one_attached&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So I asked: what if the whole thing just worked from &lt;code&gt;gem install&lt;/code&gt;?&lt;/p&gt;

&lt;h2&gt;
  
  
  What pura-image is
&lt;/h2&gt;

&lt;p&gt;A pure-Ruby implementation of decoders and encoders for the seven image formats Active Storage users actually care about:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Format&lt;/th&gt;
&lt;th&gt;Decode&lt;/th&gt;
&lt;th&gt;Encode&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;JPEG&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PNG&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BMP&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GIF&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TIFF&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ICO/CUR&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WebP&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Format detection is by magic bytes, so you don't need to trust file extensions on decode. Encode picks the format from the output extension.&lt;/p&gt;

&lt;p&gt;It also ships an &lt;code&gt;ImageProcessing::Pura&lt;/code&gt; adapter that's API-compatible with &lt;code&gt;ImageProcessing::Vips&lt;/code&gt;, which is what makes the Active Storage integration a one-line config change.&lt;/p&gt;

&lt;p&gt;As of v0.3.0, even the runtime gem dependency on &lt;code&gt;image_processing&lt;/code&gt; is gone — replaced by a small in-house &lt;code&gt;pura-processing&lt;/code&gt; gem. So "no dependencies" is now literal rather than just "no system dependencies."&lt;/p&gt;

&lt;h2&gt;
  
  
  Standalone usage
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"pura-image"&lt;/span&gt;

&lt;span class="c1"&gt;# Magic-byte detection — works on any supported format&lt;/span&gt;
&lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Pura&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"photo.jpg"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;width&lt;/span&gt;   &lt;span class="c1"&gt;#=&amp;gt; 800&lt;/span&gt;
&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;height&lt;/span&gt;  &lt;span class="c1"&gt;#=&amp;gt; 600&lt;/span&gt;

&lt;span class="c1"&gt;# Save in any format&lt;/span&gt;
&lt;span class="no"&gt;Pura&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"output.png"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Chain operations&lt;/span&gt;
&lt;span class="no"&gt;Pura&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"photo.jpg"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resize_to_limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;grayscale&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All the standard &lt;code&gt;image_processing&lt;/code&gt; operations are supported: &lt;code&gt;resize_to_limit&lt;/code&gt;, &lt;code&gt;resize_to_fit&lt;/code&gt;, &lt;code&gt;resize_to_fill&lt;/code&gt;, &lt;code&gt;resize_and_pad&lt;/code&gt;, &lt;code&gt;resize_to_cover&lt;/code&gt;, &lt;code&gt;crop&lt;/code&gt;, &lt;code&gt;rotate&lt;/code&gt;, &lt;code&gt;grayscale&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Active Storage integration
&lt;/h2&gt;

&lt;p&gt;Add the gem, set the variant processor, and you're done.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Gemfile&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"image_processing"&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"pura-image"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/application.rb&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;active_storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;variant_processor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:pura&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Existing code keeps working unchanged:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;has_one_attached&lt;/span&gt; &lt;span class="ss"&gt;:avatar&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;attachable&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;attachable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;variant&lt;/span&gt; &lt;span class="ss"&gt;:thumb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;resize_to_limit: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;image_tag&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;avatar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:thumb&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Benchmarks
&lt;/h2&gt;

&lt;p&gt;I expected pure Ruby to be uniformly slower than C. It isn't.&lt;/p&gt;

&lt;p&gt;Measurements: 400×400 image, Ruby 4.0.2 with YJIT, vs ffmpeg (C + SIMD) invoked via process spawn (the realistic comparison for a Rails app).&lt;/p&gt;

&lt;h3&gt;
  
  
  Decode
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Format&lt;/th&gt;
&lt;th&gt;pura-*&lt;/th&gt;
&lt;th&gt;ffmpeg&lt;/th&gt;
&lt;th&gt;vs ffmpeg&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TIFF&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;14 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;59 ms&lt;/td&gt;
&lt;td&gt;🚀 &lt;strong&gt;4× faster&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BMP&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;39 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;59 ms&lt;/td&gt;
&lt;td&gt;🚀 &lt;strong&gt;1.5× faster&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GIF&lt;/td&gt;
&lt;td&gt;77 ms&lt;/td&gt;
&lt;td&gt;65 ms&lt;/td&gt;
&lt;td&gt;~1× (comparable)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PNG&lt;/td&gt;
&lt;td&gt;111 ms&lt;/td&gt;
&lt;td&gt;60 ms&lt;/td&gt;
&lt;td&gt;1.9× slower&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WebP&lt;/td&gt;
&lt;td&gt;207 ms&lt;/td&gt;
&lt;td&gt;66 ms&lt;/td&gt;
&lt;td&gt;3.1× slower&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JPEG&lt;/td&gt;
&lt;td&gt;304 ms&lt;/td&gt;
&lt;td&gt;55 ms&lt;/td&gt;
&lt;td&gt;5.5× slower&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Encode
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Format&lt;/th&gt;
&lt;th&gt;pura-*&lt;/th&gt;
&lt;th&gt;ffmpeg&lt;/th&gt;
&lt;th&gt;vs ffmpeg&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TIFF&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0.8 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;58 ms&lt;/td&gt;
&lt;td&gt;🚀 &lt;strong&gt;73× faster&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BMP&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;35 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;58 ms&lt;/td&gt;
&lt;td&gt;🚀 &lt;strong&gt;1.7× faster&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PNG&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;52 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;61 ms&lt;/td&gt;
&lt;td&gt;🚀 &lt;strong&gt;faster&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JPEG&lt;/td&gt;
&lt;td&gt;238 ms&lt;/td&gt;
&lt;td&gt;62 ms&lt;/td&gt;
&lt;td&gt;3.8× slower&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GIF&lt;/td&gt;
&lt;td&gt;377 ms&lt;/td&gt;
&lt;td&gt;59 ms&lt;/td&gt;
&lt;td&gt;6.4× slower&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;5 out of 11 operations are faster than C.&lt;/strong&gt; Process-spawn overhead dominates ffmpeg's runtime for small images, and a pure-Ruby in-process call wins. JPEG and WebP are the formats where C's heavy compression algorithms still win convincingly — fine for thumbnails, not what you want for a high-volume transcoding pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Side benefits of going pure Ruby
&lt;/h2&gt;

&lt;p&gt;Beyond the original "make Rails setup easier for beginners" goal, removing the C dependency unlocks some surprising places:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Smaller Docker images&lt;/strong&gt; — drop &lt;code&gt;libvips-dev&lt;/code&gt;, &lt;code&gt;imagemagick&lt;/code&gt;, and their transitive deps from your final layer&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Faster CI&lt;/strong&gt; — no &lt;code&gt;apt-get install&lt;/code&gt; line means seconds back on every job&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PaaS without buildpack drama&lt;/strong&gt; — Heroku, Fly.io, Render all just work&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runs on ruby.wasm&lt;/strong&gt; — image processing in the browser, in a sandbox, on the edge (&lt;a href="https://komagata.github.io/pura-image/" rel="noopener noreferrer"&gt;live demo&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runs on JRuby and TruffleRuby&lt;/strong&gt; — no C extension, so no JVM/Truffle compatibility headaches&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Works on Windows&lt;/strong&gt; — no MSYS2 dance&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;I want to be upfront about where this isn't the right tool:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JPEG and WebP encoding are slower than libvips. If you're generating millions of thumbnails per hour, libvips is still the right call.&lt;/li&gt;
&lt;li&gt;Very large images (multi-thousand pixel sides) widen the gap with C implementations.&lt;/li&gt;
&lt;li&gt;The library is young. Run benchmarks and integration tests against your real workload before betting a production system on it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;This release line covers the use case I built it for: Rails Active Storage with reasonable image sizes. If you try it, I'd love to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⭐ &lt;a href="https://github.com/komagata/pura-image" rel="noopener noreferrer"&gt;Star on GitHub&lt;/a&gt; if it's useful&lt;/li&gt;
&lt;li&gt;🐛 Issues and PRs welcome — especially benchmark contributions on different workloads&lt;/li&gt;
&lt;li&gt;💬 Reply here with what you'd want to see next&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The current Rails ecosystem trend — Solid Queue, Solid Cache, Solid Cable — is removing external infrastructure dependencies in favor of "it just works with what you already have." &lt;code&gt;pura-image&lt;/code&gt; is the same idea applied to image processing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/komagata/pura-image" rel="noopener noreferrer"&gt;https://github.com/komagata/pura-image&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Browser playground (ruby.wasm): &lt;a href="https://komagata.github.io/pura-image/" rel="noopener noreferrer"&gt;https://komagata.github.io/pura-image/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Original Japanese write-up: &lt;a href="https://docs.komagata.org/6443" rel="noopener noreferrer"&gt;https://docs.komagata.org/6443&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>showdev</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
