Media

Image edits

Crop, rotate, flip, grayscale, blur, and phone photos turned upright by their EXIF orientation, saved as JPEG, PNG, WebP or GIF.

Version 1.0.0 · 6 functions · 1 crate

#images#photos#uploads#editing

Functions

What use image_ops.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use image_crop, image_rotate from image_ops (Quillo 0.6).

image_crop(from: str, to: str, x: int, y: int, w: int, h: int) → anyor an error

The part of the picture w by h pixels from its top-left corner x, y (in the upright picture), saved at to (.jpg, .png, .webp or .gif; folders made). A box reaching past the edges is cut to them; one wholly outside is an error. { width, height } of the result, as for each function here.

image_rotate(from: str, to: str, degrees: int) → anyor an error

The picture turned clockwise by degrees: 90, 180 or 270 (-90 is 270).

image_flip(from: str, to: str, direction: str) → anyor an error

The picture mirrored: direction "horizontal" (left and right swap) or "vertical" (top and bottom).

image_grayscale(from: str, to: str) → anyor an error

The picture in shades of grey (transparency kept).

image_blur(from: str, to: str, sigma: float) → anyor an error

The picture blurred, sigma the strength in pixels (2 soft, 10 strong; up to 100): to hide faces or plates, or for a background.

image_auto_orient(from: str, to: str) → anyor an error

The picture turned upright by its EXIF orientation and saved: { width, height, orientation }, orientation the one it had (1 when it was upright already). Every function here does this first, so a phone photo is cropped and turned as it is seen.

Get it

$ quillo install image_ops               # image_ops.qwer into this folder$ quillo install image_ops --example     # and its example, image_ops.qwe

Or with curl:

$ curl -O https://quillolang.org/plugins/files/image_ops.qwer$ curl -O https://quillolang.org/plugins/files/image_ops.qwe$ curl -O https://quillolang.org/plugins/files/image_ops_sample.jpg

Then, in your program, all of it or only what you call:

main.qwe
use image_ops.qweruse image_crop, image_rotate from image_ops      // Quillo 0.6: only these

It uses this crate, which cargo fetches the first time the plugin is built:

[dependencies]
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "webp", "gif"] }

Example

image_ops.qwe
// A photo editor's actions on an upload from a phone, stored sideways with an EXIF orientation.////   quillo run image_ops.qwe      then open http://localhost:8080/editsuse "image_ops.qwer"let PHOTO = "image_ops_sample.jpg"route GET /edits = {    upright: image_auto_orient(PHOTO, "data/edits/upright.jpg")?,    square: image_crop(PHOTO, "data/edits/square.jpg", 50, 0, 300, 300)?,    turned: image_rotate(PHOTO, "data/edits/turned.png", 90)?,    mirror: image_flip(PHOTO, "data/edits/mirror.webp", "horizontal")?,    grey: image_grayscale(PHOTO, "data/edits/grey.jpg")?,    blurred: image_blur(PHOTO, "data/edits/blurred.jpg", 6.0)?,    outside: is_err(image_crop(PHOTO, "data/edits/none.jpg", 900, 900, 10, 10)),}route GET /edits/:name {    let names = ["upright.jpg", "square.jpg", "turned.png", "mirror.webp", "grey.jpg", "blurred.jpg"]    if !contains(names, name) { reply 404 "No such edit" }    file(`data/edits/${name}`)}
$ quillo run image_ops.qwe$ curl 'localhost:8080/edits'

Source

image_ops.qwer
//! Everyday picture edits: crop, rotate, flip, grayscale, blur and turning phone photos upright by their EXIF orientation, saved as JPEG, PNG, WebP or GIF.//! version: 1.0.0//!//! [dependencies]//! image = { version = "0.25", default-features = false, features = ["png", "jpeg", "webp", "gif"] }use image::{DynamicImage, ImageDecoder};use serde_json::{json, Value};fn reader(path: &str) -> Result<image::ImageReader<std::io::BufReader<std::fs::File>>, String> {    let mut r = image::ImageReader::open(path).map_err(|e| format!("{}: {}", path, e))?.with_guessed_format().map_err(|e| format!("{}: {}", path, e))?;    // A small file claiming to be enormous is refused before anything is allocated.    let mut l = image::Limits::default();    l.max_image_width = Some(12_000);    l.max_image_height = Some(12_000);    l.max_alloc = Some(512 * 1024 * 1024);    r.limits(l);    Ok(r)}// The picture turned upright by its EXIF orientation, as phones expect.fn open(path: &str) -> Result<DynamicImage, String> {    let mut dec = reader(path)?.into_decoder().map_err(|e| format!("{}: {}", path, e))?;    let o = dec.orientation().unwrap_or(image::metadata::Orientation::NoTransforms);    let mut img = DynamicImage::from_decoder(dec).map_err(|e| format!("{}: {}", path, e))?;    img.apply_orientation(o);    Ok(img)}// Saved in the format its extension names: .jpg (quality 88), .png, .webp (lossless) or .gif; folders are made.fn save(img: &DynamicImage, path: &str) -> Result<(), String> {    if let Some(dir) = std::path::Path::new(path).parent().filter(|d| !d.as_os_str().is_empty()) {        std::fs::create_dir_all(dir).map_err(|e| format!("{}: {}", dir.display(), e))?;    }    let ext = path.rsplit_once('.').map(|(_, e)| e.to_lowercase()).unwrap_or_default();    let err = |e: image::ImageError| format!("{}: {}", path, e);    match ext.as_str() {        "jpg" | "jpeg" => {            let f = std::fs::File::create(path).map_err(|e| format!("{}: {}", path, e))?;            image::codecs::jpeg::JpegEncoder::new_with_quality(std::io::BufWriter::new(f), 88).encode_image(&img.to_rgb8()).map_err(err)        }        "png" => img.save_with_format(path, image::ImageFormat::Png).map_err(err),        "webp" => DynamicImage::ImageRgba8(img.to_rgba8()).save_with_format(path, image::ImageFormat::WebP).map_err(err),        "gif" => DynamicImage::ImageRgba8(img.to_rgba8()).save_with_format(path, image::ImageFormat::Gif).map_err(err),        _ => Err(format!("{}: save as .jpg, .png, .webp or .gif", path)),    }}fn done(img: &DynamicImage, to: &str) -> Result<Value, String> {    save(img, to)?;    Ok(json!({ "width": img.width(), "height": img.height() }))}/// The part of the picture `w` by `h` pixels from its top-left corner `x`, `y` (in the upright picture), saved at `to` (.jpg, .png, .webp or .gif; folders made)./// A box reaching past the edges is cut to them; one wholly outside is an error. `{ width, height }` of the result, as for each function here.pub fn image_crop(from: &str, to: &str, x: i64, y: i64, w: i64, h: i64) -> Result<Value, String> {    let img = open(from)?;    let (iw, ih) = (img.width() as i64, img.height() as i64);    let (x0, y0) = (x.max(0), y.max(0));    let (x1, y1) = ((x + w).min(iw), (y + h).min(ih));    if w <= 0 || h <= 0 || x1 <= x0 || y1 <= y0 {        return Err(format!("the box {}×{} at ({}, {}) is outside the {}×{} picture", w, h, x, y, iw, ih));    }    done(&img.crop_imm(x0 as u32, y0 as u32, (x1 - x0) as u32, (y1 - y0) as u32), to)}/// The picture turned clockwise by `degrees`: 90, 180 or 270 (-90 is 270).pub fn image_rotate(from: &str, to: &str, degrees: i64) -> Result<Value, String> {    let img = open(from)?;    let out = match degrees.rem_euclid(360) {        0 => img,        90 => img.rotate90(),        180 => img.rotate180(),        270 => img.rotate270(),        d => return Err(format!("turn by 90, 180 or 270 degrees, not {}", d)),    };    done(&out, to)}/// The picture mirrored: `direction` "horizontal" (left and right swap) or "vertical" (top and bottom).pub fn image_flip(from: &str, to: &str, direction: &str) -> Result<Value, String> {    let img = open(from)?;    let out = match direction.to_lowercase().as_str() {        "horizontal" | "h" => img.fliph(),        "vertical" | "v" => img.flipv(),        d => return Err(format!("flip \"horizontal\" or \"vertical\", not `{}`", d)),    };    done(&out, to)}/// The picture in shades of grey (transparency kept).pub fn image_grayscale(from: &str, to: &str) -> Result<Value, String> {    let img = open(from)?;    let out = if img.color().has_alpha() { DynamicImage::ImageLumaA8(img.to_luma_alpha8()) } else { DynamicImage::ImageLuma8(img.to_luma8()) };    done(&out, to)}/// The picture blurred, `sigma` the strength in pixels (2 soft, 10 strong; up to 100): to hide faces or plates, or for a background.pub fn image_blur(from: &str, to: &str, sigma: f64) -> Result<Value, String> {    let img = open(from)?;    done(&img.blur(sigma.clamp(0.1, 100.0) as f32), to)}/// The picture turned upright by its EXIF orientation and saved: `{ width, height, orientation }`, `orientation` the one it had (1 when it was upright already)./// Every function here does this first, so a phone photo is cropped and turned as it is seen.pub fn image_auto_orient(from: &str, to: &str) -> Result<Value, String> {    let mut dec = reader(from)?.into_decoder().map_err(|e| format!("{}: {}", from, e))?;    let o = dec.orientation().unwrap_or(image::metadata::Orientation::NoTransforms);    let code = match o {        image::metadata::Orientation::NoTransforms => 1,        image::metadata::Orientation::FlipHorizontal => 2,        image::metadata::Orientation::Rotate180 => 3,        image::metadata::Orientation::FlipVertical => 4,        image::metadata::Orientation::Rotate90FlipH => 5,        image::metadata::Orientation::Rotate90 => 6,        image::metadata::Orientation::Rotate270FlipH => 7,        image::metadata::Orientation::Rotate270 => 8,    };    let mut img = DynamicImage::from_decoder(dec).map_err(|e| format!("{}: {}", from, e))?;    img.apply_orientation(o);    save(&img, to)?;    Ok(json!({ "width": img.width(), "height": img.height(), "orientation": code }))}
EXIF exif.qwer · 1.0.0A photo's EXIF: camera and lens, when it was taken, where (GPS), exposure and orientation, from JPEG, HEIC, PNG and WebP.imagesphotosplacesuploads7 views · 5 downloadsEXIF strip exif_strip.qwer · 1.0.0Photos made safe to publish: GPS, camera and other EXIF, XMP, IPTC and comments taken out of JPEG, PNG and WebP, without re-encoding.imagesphotosprivacyuploads6 views · 7 downloadsOCR ocr.qwer · 1.0.0Text read from pictures in pure Rust: photos and scans of documents, receipts, labels and screenshots to plain text, row by row or line by line with boxes, parts of the page read by range (pixels or percentages), several form fields at once, only the characters you allow, and phone photos turned upright first.ocrimagesphotostextdocumentsreceiptsinvoicesformsuploads13 views · 9 downloadsWatermark watermark.qwer · 1.0.0A logo laid over photos at a corner or the centre, or tiled across, with the opacity you choose, for publishing pictures with your mark.imagesphotosbrandinguploads8 views · 8 downloadsCaptions on images caption.qwer · 1.0.0Text drawn on pictures with a built-in font: captions at the top, middle or bottom, wrapped to fit, with an outline or a band behind them, meme-style top and bottom lines, and text measured before drawing.imagesphotosfontssocial7 views · 7 downloadsCollage collage.qwer · 1.0.0Several pictures made into one: a grid of equal cells with gaps, or justified rows that keep each picture's shape, on your background.imagesphotossocial7 views · 7 downloads
Thread
77Useful? Like it, or add to its thread below.

Thread 0 posts

On Image edits: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.