Initial Commit

This commit is contained in:
markmental 2025-11-21 02:56:46 +00:00
commit 77e8c510f4
5 changed files with 527 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
public/
*.lock

7
Gemfile Normal file
View file

@ -0,0 +1,7 @@
source "https://rubygems.org"
gem "sinatra"
gem "sinatra-contrib"
gem "mini_magick"
gem "rackup"
gem "puma"

223
README.md Normal file
View file

@ -0,0 +1,223 @@
# 📸 Rubymagick — Simple Ruby Image Converter & Pixelation Tool
**Rubymagick** is a tiny Ruby/Sinatra web app for converting images (like PNGs) into **JPEGs**, shrinking file sizes, or applying **fun retro effects** such as CRT scanlines and adjustable pixelation.
Its designed to be lightweight, easy to run locally, and perfect for:
* Compressing PNGs into small JPGs
* Preparing images for websites or blog posts
* Pixelating art or photos (SNES / PS1 / Game Boy style)
* Adding fake CRT scanlines for retro aesthetics
* Quickly batch-converting images through a browser
No account, no cloud upload — just run it on your own machine.
---
## ✨ Features
### ✔️ JPEG Conversion
Upload any image (PNG, JPG, WebP, HEIC, etc.) and convert it into a JPEG with adjustable quality.
### ✔️ Adjustable JPEG Quality Slider
Choose any value from **10100**. Lower quality = smaller file size.
### ✔️ Pixelation Effect (with slider)
A fully adjustable pixelation slider:
* Factor **2** → subtle pixelation
* Factor **8** → chunky SNES/GBA style
* Factor **1225** → extreme blocky PS1 / Minecraft look
### ✔️ CRT / Scanline Effect
Simulates a low-resolution CRT display:
* Downscales to fake 480p
* Adds contrast + noise
* Optionally overlays scanlines if `public/scanlines.png` exists
### ✔️ Local, Private, Simple
Everything runs on your machine.
Nothing leaves your computer.
---
## 🛠 Requirements
You need the following gems:
```
sinatra
sinatra-contrib
mini_magick
rackup
puma
```
Install them with:
```bash
gem install sinatra sinatra-contrib mini_magick rackup puma
```
And you must have ImageMagick installed:
### Debian/Ubuntu
```bash
sudo apt install imagemagick
```
### Fedora/Rocky/RHEL
```bash
sudo dnf install imagemagick
```
### Arch
```bash
sudo pacman -S imagemagick
```
---
## 🚀 Running the App
Save the project as `app.rb`, then run:
```bash
bundle exec ruby app.rb
```
Or if you're not using Bundler:
```bash
ruby app.rb
```
A provided `start-server.sh` Bash script is also available
Then visit:
```
http://localhost:4567/
```
---
## 🖼 How to Use
1. Open the web UI.
2. Upload an image (PNG, JPG, WEBP, etc.)
3. Choose your desired JPEG output quality.
4. Select an effect (None, CRT, or Pixelated).
5. If you choose Pixelated → adjust the intensity slider.
6. Click **Convert to .jpg**.
7. Download your JPEG file.
Converted files are stored in:
```
public/output/
```
---
## 🎛 Effects Explained
### 🔸 CRT / Scanline Mode
This effect simulates an old-school CRT monitor:
* Resizes image down to low resolution
* Boosts contrast
* Adds light noise for texture
* If `public/scanlines.png` exists, overlays it
Good for:
* Retro edits
* Vaporwave / synthwave style
* Terminal-style effects
---
### 🔸 Pixelation Mode (Adjustable)
Uses nearest-neighbor downscale → upscale trick.
Formula:
```
small_w = width / factor
small_h = height / factor
```
Then scales back up with no filtering.
Great for:
* Pixel art effects
* Lo-fi game textures
* Retro aesthetic
* Meme-style blur blocks
---
## 📁 Optional: Add Custom Scanlines
Place a PNG named:
```
public/scanlines.png
```
It will be automatically applied when CRT mode is selected.
---
## 🧩 Project Structure
Typical layout:
```
rubymagick/
├─ app.rb
├─ Gemfile
├─ public/
│ ├─ output/
│ └─ scanlines.png (optional)
```
Output files go into `public/output/`.
---
## 📝 About This App
Rubymagick is intentionally small and hackable.
Its perfect as:
* A personal utility
* A learning project for Sinatra
* A way to explore ImageMagick through Ruby
* A lightweight image-prep tool for websites
You can expand it easily with:
* WebP/AVIF export
* Batch uploads
* Color palette reduction (NES/Game Boy)
* VHS distortion effects
* CLI wrapper mode
Just modify the helper functions or add more UI elements.
---

292
app.rb Normal file
View file

@ -0,0 +1,292 @@
#!/usr/bin/env ruby
# app.rb — Simple Ruby/Sinatra Image → JPEG converter
# Supports:
# - JPEG quality slider
# - CRT/scanline retro effect
# - Pixelation (with user slider)
#
# Requires:
# gem install sinatra sinatra-contrib mini_magick rackup puma
# sudo apt install imagemagick
#
# Run:
# bundle exec ruby app.rb
#
# Then open: http://localhost:4567/
require "sinatra"
require "sinatra/reloader" if development?
require "mini_magick"
require "fileutils"
require "securerandom"
set :bind, "0.0.0.0"
set :port, 4567
set :public_folder, File.join(__dir__, "public")
OUTPUT_DIR = File.join(settings.public_folder, "output")
FileUtils.mkdir_p OUTPUT_DIR
# -------------------------------------------------------------------
# HELPERS — Image effects + utilities
# -------------------------------------------------------------------
helpers do
# -----------------------------------------------------------------
# CRT / Scanline effect
# -----------------------------------------------------------------
def apply_crt_effect(image)
image.combine_options do |c|
c.resize "640x480"
c.contrast
c.noise "2"
end
scanlines_path = File.join(settings.public_folder, "scanlines.png")
if File.exist?(scanlines_path)
scan = MiniMagick::Image.open(scanlines_path)
scan.resize "#{image.width}x#{image.height}!"
image = image.composite(scan) do |c|
c.compose "overlay"
c.gravity "center"
end
end
image
end
# -----------------------------------------------------------------
# Pixelation effect with user-adjustable factor
# -----------------------------------------------------------------
def apply_pixelate_effect(image, factor = 8)
width = image.width
height = image.height
small_w = [width / factor, 1].max
small_h = [height / factor, 1].max
image.combine_options do |c|
c.filter "point"
c.resize "#{small_w}x#{small_h}!"
c.resize "#{width}x#{height}!"
end
image
end
# Human-readable formatting
def human_size(bytes)
kb = bytes / 1024.0
return "#{kb.round(1)} KB" if kb < 1024
mb = kb / 1024.0
"#{mb.round(2)} MB"
end
end
# -------------------------------------------------------------------
# ROUTES
# -------------------------------------------------------------------
get "/" do
erb :index
end
post "/convert" do
unless params[:image] && params[:image][:tempfile]
@error = "Please choose an image file."
return erb :index
end
tempfile = params[:image][:tempfile]
filename = params[:image][:filename]
quality = (params[:quality] || "80").to_i
effect = params[:effect]
quality = 1 if quality < 1
quality = 100 if quality > 100
begin
image = MiniMagick::Image.open(tempfile.path)
# ------------------------------
# Apply effects
# ------------------------------
case effect
when "crt"
image = apply_crt_effect(image)
@effect_used = "CRT / scanline"
when "pixel"
factor = (params[:pixel_factor] || 8).to_i
factor = 2 if factor < 2
factor = 25 if factor > 25
image = apply_pixelate_effect(image, factor)
@effect_used = "Pixelated (factor #{factor})"
else
@effect_used = "None"
end
# Convert to JPEG
image.format "jpg"
image.quality quality.to_s
output_name = "converted-#{SecureRandom.hex(8)}.jpg"
output_path = File.join(OUTPUT_DIR, output_name)
image.write(output_path)
@original_name = filename
@output_url = "/output/#{output_name}"
@output_size = human_size(File.size(output_path))
@chosen_quality = quality
erb :result
rescue => e
@error = "Conversion failed: #{e.message}"
erb :index
end
end
# -------------------------------------------------------------------
# INLINE HTML TEMPLATES
# -------------------------------------------------------------------
__END__
@@layout
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Rubymagick JPG Converter</title>
<style>
body {
background: #111;
color: #eee;
font-family: system-ui, sans-serif;
max-width: 800px;
margin: 2rem auto;
padding: 0 1rem;
}
h1, h2 { color: #6cf; }
.card {
background: #1b1b1b;
border-radius: 8px;
padding: 1.5rem;
box-shadow: 0 0 15px rgba(0,0,0,0.6);
border: 1px solid #333;
}
label {
display: block;
margin-top: 1rem;
margin-bottom: 0.25rem;
font-weight: 600;
}
input[type="file"],
select,
button,
input[type="range"] {
width: 100%;
padding: 0.5rem;
border-radius: 4px;
border: 1px solid #333;
background: #222;
color: #eee;
}
button {
margin-top: 1.5rem;
background: #0a84ff;
cursor: pointer;
border: none;
font-weight: 600;
}
button:hover { background: #0062cc; }
img.preview {
max-width: 100%;
border-radius: 8px;
margin-top: 1rem;
border: 1px solid #333;
}
</style>
</head>
<body>
<h1> Image JPEG Converter</h1>
<%= yield %>
</body>
</html>
@@index
<div class="card">
<% if @error %><div class="error"><%= @error %></div><% end %>
<form action="/convert" method="post" enctype="multipart/form-data">
<label for="image">Image file</label>
<input type="file" id="image" name="image" accept="image/*" required>
<label for="quality">JPEG quality:
<span id="qualityValue">80</span>
</label>
<input
type="range"
id="quality"
name="quality"
min="10"
max="100"
value="80"
oninput="document.getElementById('qualityValue').textContent = this.value"
>
<label for="effect">Effect</label>
<select id="effect" name="effect">
<option value="none">None</option>
<option value="crt">CRT / Scanline</option>
<option value="pixel">Pixelated</option>
</select>
<!-- Pixelation slider (hidden until selected) -->
<div id="pixelControls" style="display:none;">
<label for="pixel_factor">Pixelation intensity:
<span id="pixelFactorValue">8</span>
</label>
<input
type="range"
id="pixel_factor"
name="pixel_factor"
min="2"
max="25"
value="8"
oninput="document.getElementById('pixelFactorValue').textContent = this.value"
>
</div>
<script>
const effectSelect = document.getElementById("effect");
const pixelControls = document.getElementById("pixelControls");
effectSelect.addEventListener("change", () => {
pixelControls.style.display =
effectSelect.value === "pixel" ? "block" : "none";
});
</script>
<button type="submit">Convert to .jpg</button>
</form>
</div>
@@result
<div class="card">
<h2>Conversion complete</h2>
<p>
Original: <strong><%= @original_name %></strong><br>
Quality: <strong><%= @chosen_quality %></strong><br>
Effect: <strong><%= @effect_used %></strong><br>
Output size: <strong><%= @output_size %></strong>
</p>
<img class="preview" src="<%= @output_url %>" alt="Converted image preview">
<br>
<a class="download" href="<%= @output_url %>" download>Download JPEG</a>
</div>

3
start-server.sh Executable file
View file

@ -0,0 +1,3 @@
#!/bin/bash
bundle3.3 exec ruby app.rb