Editing pictures
Obraz edits pictures with the tools of a photo editor: crop, turn, resize and frame a picture; change its tone, give it a look or a colour grade; blur, mosaic, sharpen, vignette and grain it; write text, lay stickers and shapes on it; put it on a canvas or several pictures in a collage; and, through an image-model service, let a model generate a picture, remove or replace its background, erase an object, extend the frame, restyle, colourise, restore, retouch or relight it.
Three ways reach it: the editor in a browser at the
address Obraz serves, POST /v1/edits for programs, and
obraz::client::ObrazClient::edit for Rust callers. New to Obraz?
Start with the quickstart.
An edit is one plan: a list of steps run in order on a
working picture. The working picture starts as input 0, or
is made by a first blank, collage or
generate step. Every step names its values; where a field may be
left out, leaving it out leaves the picture as it is (no turn, full opacity,
normal blending, an overlay's own size) or uses what the font says (its line
height). Obraz chooses no strength, size or colour for anybody.
The whole plan is checked before any pixel changes: a plan that would be refused at step nine does not spend a model call at step two. What only the running picture can answer, such as a crop outside a picture whose size earlier steps decided, is refused when that step runs.
The endpoint
POST /v1/edits run one plan, answer the edited picture
GET /v1/edits/operations the steps a plan can hold, with their fields
POST /v1/edits
x-obraz-secret: <the deployment's secret>
{"plan": {"steps": [
{"op": "crop_ratio", "ratio": "4:5", "anchor": "center"},
{"op": "resize", "width": 1080, "height": 1350, "fit": "cover", "filter": "lanczos3"},
{"op": "adjust", "contrast": 0.15, "saturation": 0.25},
{"op": "text", "text": "Summer in the city", "font": "brand", "size": 96,
"color": "#ffffff", "x": 540, "y": 1280, "anchor": "bottom",
"stroke": {"width": 4, "color": "#000000"}}]},
"inputs": ["<base64 of a picture>"],
"assets": {"brand": "<base64 of a TTF or OTF font>"},
"output": {"format": "png"}}
inputs: pictures as base64, in any format the Rustimagecrate reads (PNG, JPEG, WebP, GIF, BMP, TIFF and others); steps name them by position, input 0 first.assets: named files as base64 — fonts fortext,.cubetables forlut.output:formatispng,jpeg,webp,tiff,bmporgif; JPEG needsqualityon the encoder's scale, 1 to 100, and no other format takes one. JPEG holds no transparency, so a picture with a transparent pixel is refused rather than flattened onto a colour nobody chose.
The answer is success, job_id,
mime_type, width, height,
image_base64, and steps: for every step its number
(from 1), its op, the size of the picture it left, and for a
model step the route: the model the image-model service says answered. A refusal is
success: false with error:
{"step", "op", "reason"}, or only reason when the
refusal is about the whole request. A Rust caller uses
obraz::client::ObrazClient::edit.
Any field a step does not take, and an op Obraz does not have,
is refused by the JSON reader with 422 and its own sentence,
which names the field or the variant, for example unknown variant
`sparkle`
.
Values every step uses
- Colours are
#RRGGBBor#RRGGBBAA; without the alpha pair a colour is opaque. - Anchors say which point of a box a position holds:
top_left,top,top_right,left,center,right,bottom_left,bottom,bottom_right. - Fits:
stretchdistorts a picture to a frame,containshows all of it and fills the rest,coverfills the frame and cuts what sticks out. - Filters for resampling, as the
imagecrate names them:nearest,triangle,catmull_rom,gaussian,lanczos3. - Fills paint what a picture does not cover:
{"color": "#RRGGBB"};{"blur": SIGMA}, the working picture covering the frame and blurred, opaque even where the picture is transparent;{"gradient": {"from", "to", "angle"}}, 0 degrees running left to right and 90 top to bottom;{"input": N}, an input picture covering the frame. Pictures are resampled for a fill, a collage and an overlay with Lanczos3. - Blends for an overlay:
normal,multiply,screen,overlay,darken,lighten,color_dodge,color_burn,hard_light,soft_light,difference,exclusion,hue,saturation,color,luminosity,add. - Regions are
{"x", "y", "width", "height"}in pixels from the top-left corner, and must lie inside the picture. - Opacity is a share from 0 to 1.
Canvas
| op | fields | what it does |
|---|---|---|
blank | width, height, fill | Starts from an empty frame painted with the fill; a blur fill needs a picture and is refused here. |
collage | inputs, columns, cell_width, cell_height, gap, fit, fill | Lays the named inputs in a grid of columns, each fitted to its cell and centred; gap pixels around and between the cells show the fill. |
canvas | width, height, anchor, fill | Puts the picture on a frame of a new size, held to the anchor: the size of a post, a story or a cover. What it does not cover shows the fill; what sticks out is cut. |
Geometry
| op | fields | what it does |
|---|---|---|
crop | x, y, width, height | Keeps one rectangle. |
crop_ratio | ratio ("W:H"), anchor | Keeps the largest rectangle of that ratio, held to the anchor. |
rotate | degrees (clockwise), fill (a colour) | A quarter turn is exact. Any other angle grows the frame to hold the whole turned picture and needs fill for the corners it uncovers. |
flip | axis: horizontal or vertical | Mirrors the picture. |
resize | width, height, fit, filter, fill | Fits the picture to a frame. A contain fit centres it and needs a fill for the bars; stretch and cover take none. |
scale | factor, filter | Multiplies both sides by the factor: enlarge or reduce. |
mask | shape: ellipse or rounded_rectangle; radius (rounded rectangle only) | Makes everything outside the shape transparent: a round avatar, rounded corners. |
border | width, color | Frames the picture; it grows by twice the width on each axis. |
Tone and colour
| op | fields | what it does |
|---|---|---|
adjust | any of exposure, brightness, contrast, highlights, shadows, saturation, vibrance, temperature, tint, hue, fade, gamma | The tone controls, applied in that order on channels scaled 0 to 1: exposure in stops (each one doubles the light); brightness added; contrast spread around middle grey by 1 + contrast; highlights and shadows added in proportion to how far a pixel's luma sits above or below middle grey; saturation spread around luma by 1 + saturation; vibrance the same, weighted toward the least saturated colours; temperature added to red and taken from blue; tint taken from green, so a positive tint leans magenta; hue turned by degrees; fade moved toward middle grey; gamma the power 1 / gamma. A control left out is not touched; a step naming none is refused. |
filter | look: grayscale, sepia or invert; intensity | A look mixed in by intensity, 0 leaving the picture and 1 the full look. Grayscale is Rec. 709 luma; sepia is the matrix of the W3C Filter Effects sepia() function. |
duotone | shadows, highlights (colours), intensity | Maps light onto the line between two colours. |
posterize | levels (at least 2) | Holds every channel to that many evenly spaced values. |
lut | asset, intensity | Grades the picture with a 3D colour lookup table in the Adobe .cube format (LUT_3D_SIZE, DOMAIN_MIN/DOMAIN_MAX or Resolve's LUT_3D_INPUT_RANGE), interpolated between the eight entries around each colour. |
Effects
| op | fields | what it does |
|---|---|---|
blur | sigma, region (optional) | Gaussian blur of the whole picture or one region; nothing outside the region moves. |
pixelate | block, region (optional) | Mosaic: every square of block pixels takes its average colour — to hide a face or a number plate. |
sharpen | sigma, amount | Unsharp mask: the picture plus amount times its difference from a blur of sigma. A large sigma raises local contrast, which photo editors call clarity. |
vignette | amount, radius, softness | Darkens toward the corners (a negative amount lightens). Distance is a share of half the diagonal from the centre; the change starts at radius and reaches amount at radius + softness. |
grain | amount, seed | Film grain: the same noise on all three channels, between −amount and +amount, from SplitMix64 seeded by seed, so one plan always gives one picture. |
Text, stickers and shapes
| op | fields | what it does |
|---|---|---|
text | text, font (a font asset), size, color, x, y, anchor; optional align (left, center, right), line_height, max_width, stroke {"width", "color"}, shadow {"dx", "dy", "blur", "color"}, background {"color", "padding"}, rotation, opacity | Writes text with the caller's font; the anchor names which point of the text block sits at (x, y). Lines break at \n and, with max_width, at spaces; text over several lines needs align. Without line_height the font's own line spacing is used. |
overlay | input, x, y; optional width, height, rotation, opacity, blend | Lays another input picture over this one with its top-left corner at (x, y): a sticker, a logo, a picture in picture. One stated side keeps the input's proportions; the rotation turns it about its own centre. |
shape | shape: rectangle, ellipse or line; x, y, width, height; optional fill (a colour), stroke, corner_radius (rectangle only), rotation, opacity | A rectangle or ellipse fills the box at (x, y); a line runs from (x, y) to (x + width, y + height) and needs a stroke. |
Model steps
These steps are done by an image model, through the image-model service
the deployment names (configuration):
any service that speaks the OpenAI Images API. In a Wisent deployment that is
Brama, whose image-model alias the deployment names and which
owns which provider answers and who pays; outside one it can be the OpenAI API
itself. A generation is POST /v1/images/generations; an edit is
POST /v1/images/edits as the OpenAI edit form, the working picture
as the image file and the mask as the mask file. A
step asks the model it names in model, or the deployment's
OBRAZ_IMAGE_MODEL. Every model step is a billed provider
call.
A model answers at a size of its own, so an edit is resized back to the
size of the picture it was given (Lanczos3). erase and
expand keep the original pixels everywhere outside the area the
model was asked to paint.
| op | fields | what Obraz asks for |
|---|---|---|
generate | prompt; optional size, aspect_ratio, quality (the provider's own strings), model | A picture from the description, at the size the model answers with. |
ai_edit | prompt, model | The picture changed as the prompt says. |
remove_background | model | Remove the background completely, leaving only the main subject on a transparent background. Do not change the subject., with background: transparent. |
replace_background | prompt, model | Keep the main subject exactly as it is and replace everything behind it with:and the prompt. |
erase | region, model | Remove whatever is in the masked area and fill it so that it matches its surroundings, as if it had never been there.The mask is transparent over the region; only the region of the answer is kept. |
expand | left, top, right, bottom; optional prompt, model | The frame grows by those pixels with a transparent border, which is the mask: Extend this picture into the transparent border so that the new area continues the scene seamlessly. Do not change the existing picture.and the prompt. The original is put back unchanged. |
restyle | style, model | Redraw this picture in the following style, keeping its composition and subjects:and the style. |
colorize | model | Colorize this black-and-white photograph with natural, realistic colours. Change nothing else. |
restore | model | Restore this old or damaged photograph: remove scratches, dust, tears, stains and noise and recover lost detail. Keep every person and object as they are. |
retouch | model | Retouch the people in this portrait: soften skin blemishes and even out skin tone while keeping their identity, features and expression. Change nothing else. |
relight | lighting, model | Relight this picture with the following lighting, keeping its subjects and composition:and the lighting. |
The service's refusal comes back in its own words as the step's reason,
with 502: the image model service at <address> refused
(<status>): <code or type>: <message>
— for example an
unrouted image-model on Brama, a key the provider rejects, or an
account with no credit. Other answers Obraz cannot use are named:
the image model service at <address> could not be reached: <cause>
,
the image model service at <address> answered <status> with a body that is not JSON: <body>
,
the image model service at <address> answered <status> with no image in `data`: <body>
,
the image model service's image carries neither b64_json nor url: <item>
,
the image model service's b64_json is not base64: <cause>
,
the picture at <url> answered <status>
,
OBRAZ_IMAGE_MODEL_TOKEN_FILE <path> cannot be read: <cause>
.
The editor
obraz serve answers GET / with the editor: a page
where a person adds pictures and assets, starts from an example (a 4:5 post, a
9:16 story on a blurred backdrop, a round avatar, a mosaic over a face, a logo
watermark, a collage, a product photo on white), adds steps from the list
GET /v1/edits/operations gives, and presses Edit. The page
sends the same POST /v1/edits any caller sends, with the caller
secret the person types, and shows the picture, its size after every step, a
download link, or the refusal with its status, step and reason.
Refusals
400, before anything is edited:
the plan has no steps
no input picture was given and the first step edits one: give an input, or begin with blank, collage or generate
input <n> is not base64: <cause>
input <n> is not a picture Obraz can read: <cause>
asset <name> is not base64: <cause>
a JPEG output needs a quality from 1 to 100
JPEG quality is 1 to 100 on the encoder's scale, not <quality>
quality applies to JPEG only; <mime type> is written without one
and, prefixed step <n> (<op>):
:
this step is done by an image model, and this Obraz has no image-model service: its deployment sets OBRAZ_IMAGE_MODEL_URL, OBRAZ_IMAGE_MODEL_TOKEN_FILE, OBRAZ_IMAGE_MODEL
input <n> was named and <count> input picture(s) were given
<width>x<height> has no pixels
a blur fill blurs the working picture, and a blank canvas starts without one
a collage needs at least one input and one column
ratio "<ratio>" is not two whole numbers above zero as W:H
a turn of <degrees> degrees uncovers corners: name the fill colour for them
a contain resize leaves bars: name the fill for them
only a contain resize leaves room for a fill; stretch and cover fill the frame
a rounded_rectangle mask needs a radius
an ellipse mask takes no radius
a border needs a width of at least 1 pixel
adjust names no control: give at least one of brightness, contrast, saturation, vibrance, exposure, temperature, tint, highlights, shadows, hue, fade, gamma
posterize needs at least 2 levels, not <levels>
the <font|LUT> asset "<name>" was not given; the assets given are [<names>]
LUT asset "<name>": line <n>: LUT_3D_SIZE must be a whole number of at least 2
LUT asset "<name>": line <n>: LUT_3D_INPUT_RANGE needs two numbers
LUT asset "<name>" is not text
LUT asset "<name>": declares no LUT_3D_SIZE
LUT asset "<name>": is a 1D table; Obraz applies 3D tables (LUT_3D_SIZE)
LUT asset "<name>": LUT_3D_SIZE <size> needs <count> entries and the file has <count>
LUT asset "<name>": line <n> is not three numbers
LUT asset "<name>": DOMAIN_MAX must lie above DOMAIN_MIN on every channel
a pixelate block must be at least 1 pixel
font asset "<name>" is not a font Obraz can read: <cause>
text has nothing to write
text that runs over several lines needs align: left, center or right
an overlay's width and height must be at least 1 pixel
a line has no inside to fill; give it a stroke
a line needs a stroke to be seen
only a rectangle takes a corner_radius
a shape needs a fill, a stroke or both
expand needs at least one side to grow
<field> must be a number, not <value>
<field> must be above 0, not <value>
<field> must be 0 or more, not <value>
opacity is a share from 0 to 1, not <value>
<prompt|style|lighting> must say something
422, while editing — a step the picture as it then is cannot
take, or a result the format cannot hold:
step <n> (<op>): the region <w>x<h> at (<x>, <y>) reaches (<right>, <bottom>), outside the <width>x<height> picture
step <n> (scale): scaling the <width>x<height> picture by <factor> leaves no pixel
the picture has transparent pixels and JPEG cannot hold them: fill the background first (canvas or blank with a fill) or write PNG or WebP
502: a model step that the image-model service or its provider refused, as
above. 401 unauthorized
: a missing or wrong
x-obraz-secret.