RIAPI Adapters

Convert crop selections to server-compatible querystrings. Three built-in adapters cover all RIAPI-compliant servers.

What is RIAPI?

RIAPI (RESTful Image API) is a querystring-based convention for image manipulation. Instead of uploading a modified image, you describe the transformation as URL parameters and the server applies them on-the-fly. The crop parameter specifies a rectangle; cropxunits and cropyunits define the coordinate system.

Choosing an Adapter

Set the adapter attribute on the element. This controls how the crop selection is serialized to a querystring.

AdapterServerPadding
genericAny RIAPI serverCrop only (no padding output)
imageflowImageflow Servers.pad=T,R,B,L (source pixels)
imageresizerImageResizer (v3/v4/v5)margin=T,R,B,L (source pixels)
<!-- Generic (default) -->
<crop-image src="/photo.jpg"></crop-image>

<!-- Imageflow -->
<crop-image src="/photo.jpg" adapter="imageflow"></crop-image>

<!-- ImageResizer -->
<crop-image src="/photo.jpg" adapter="imageresizer"></crop-image>

Output Format

Generic Adapter

All adapters use fractional units (cropxunits=1, cropyunits=1), so crop coordinates map directly to 0..1 fractions of the source image.

?crop=0.1,0.15,0.9,0.85&cropxunits=1&cropyunits=1

The generic adapter does not output padding parameters. Use it when your server doesn't support letterboxing, or when you're handling padding separately.

Imageflow Adapter

Crop uses the same crop/cropxunits/cropyunits parameters. When padding is present (crop-pad mode), it's serialized as s.pad=T,R,B,L in source pixels:

?crop=0,0.1,1,0.9&cropxunits=1&cropyunits=1&s.pad=50,0,50,0

Padding values are Math.round(fraction * sourceDimension). For a 1000x800 image with 5% top padding: 0.05 * 800 = 40px.

Imageflow server note: Imageflow natively supports crop, cropxunits, and cropyunits. It also supports the shorthand c=x1,y1,x2,y2 which automatically sets units to 100 (percentage mode). Padding in Imageflow is typically handled by the layout engine via mode=pad with target w/h dimensions rather than explicit padding parameters. The s.pad output from this adapter is intended for custom server-side handling — your application code should interpret it and apply padding accordingly.

ImageResizer Adapter

Same crop format, with padding serialized as margin=T,R,B,L in source pixels:

?crop=0,0.1,1,0.9&cropxunits=1&cropyunits=1&margin=50,0,50,0

ImageResizer note: margin was part of the ImageResizer v3/v4 spec. Imageflow recognizes the key but has not yet implemented parsing for it. If your server doesn't handle margin, use the generic adapter and process padding in your application code.

Bidirectional Conversion

All adapters support both directions: selection-to-querystring and querystring-to-selection. This is useful for restoring a previously saved crop.

Using the Core Library Directly

import {
  GenericRiapiAdapter,
  ImageflowAdapter,
  ImageResizerAdapter,
  parseQuerystring,
} from '@imazen/crop-image-core';

// Selection → querystring
const adapter = new ImageflowAdapter();
const result = adapter.toParams(selection, sourceWidth, sourceHeight);
console.log(result.querystring);
// "?crop=0.1,0.1,0.9,0.9&cropxunits=1&cropyunits=1"

// Querystring → selection
const params = parseQuerystring('?crop=0.1,0.1,0.9,0.9&cropxunits=1&cropyunits=1');
const restored = adapter.fromParams(params, sourceWidth, sourceHeight);
// { crop: { x1: 0.1, y1: 0.1, x2: 0.9, y2: 0.9 }, pad: { top: 0, ... } }

The RiapiAdapter Interface

All adapters implement this interface from @imazen/crop-image-core:

interface RiapiResult {
  params: Record<string, string>;
  querystring: string;
}

interface RiapiAdapter {
  toParams(sel: CropSelection, srcW: number, srcH: number): RiapiResult;
  fromParams(params: Record<string, string>, srcW: number, srcH: number): CropSelection | null;
}

Custom Adapters

You can implement the RiapiAdapter interface for your own server's querystring format. The web component doesn't natively support custom adapter instances through attributes, but you can use the core library directly:

import { type RiapiAdapter, type RiapiResult, type CropSelection } from '@imazen/crop-image-core';

class MyServerAdapter implements RiapiAdapter {
  toParams(sel: CropSelection, srcW: number, srcH: number): RiapiResult {
    const { x1, y1, x2, y2 } = sel.crop;
    const params: Record<string, string> = {
      'rect': `${Math.round(x1 * srcW)},${Math.round(y1 * srcH)},` +
              `${Math.round((x2 - x1) * srcW)},${Math.round((y2 - y1) * srcH)}`,
    };
    const querystring = '?' + Object.entries(params)
      .map(([k, v]) => `${k}=${v}`).join('&');
    return { params, querystring };
  }

  fromParams(params: Record<string, string>, srcW: number, srcH: number): CropSelection | null {
    // Parse your format back to CropSelection
    return null;
  }
}

// Use with the web component via events
const adapter = new MyServerAdapter();
const cropper = document.querySelector('crop-image');

cropper.addEventListener('crop-commit', (e) => {
  const sel = e.detail.selection;
  const result = adapter.toParams(sel, sourceWidth, sourceHeight);
  console.log(result.querystring);
});

Utility Functions

The core package exports helpers for working with querystrings:

import { buildQuerystring, parseQuerystring } from '@imazen/crop-image-core';

// Build from key-value pairs
const qs = buildQuerystring({ crop: '0.1,0.1,0.9,0.9', cropxunits: '1', cropyunits: '1' });
// "?crop=0.1%2C0.1%2C0.9%2C0.9&cropxunits=1&cropyunits=1"

// Parse back to object
const params = parseQuerystring(qs);
// { crop: "0.1,0.1,0.9,0.9", cropxunits: "1", cropyunits: "1" }

Coordinate System

All adapters use the same coordinate system internally:

Imageflow's c= Shorthand

Imageflow also supports c=x1,y1,x2,y2 which automatically sets cropxunits=100 and cropyunits=100 (percentage mode). A crop of c=10,20,90,80 means 10%-90% horizontally, 20%-80% vertically. The adapters use fractional crop= instead, but if you're building URLs by hand, c= is more concise.

RIAPI Execution Order

In both Imageflow and ImageResizer, the processing pipeline is:

trim → srotate → sflip → crop → scale → filter → pad → rotate → flip

Crop coordinates refer to the source image before any scaling. Padding is applied after scaling.

Padding is only generated in crop-pad mode. In standard crop mode, the crop is constrained to 0..1 bounds and padding is always zero. Switch to mode="crop-pad" to allow the selection to extend beyond the image edges.

.NET Integration

The crop-image component was designed to pair with .NET image servers. The RIAPI querystring output plugs directly into Imageflow or ImageResizer.

Imageflow Server (.NET)

Append the querystring to your image URL. Imageflow processes it on the fly.

// Razor Pages / MVC
<img src="/images/@Model.Filename@Model.CropQuerystring&width=800" />

// Or build the URL in C#
var croppedUrl = $"/images/{filename}{cropQs}&width=800&format=webp";

ImageResizer (.NET)

Same approach — the crop and cropxunits/cropyunits params are native ImageResizer syntax.

// ASP.NET MVC
<img src="@Url.Content("~/photos/" + Model.Photo)@Model.CropParams&width=600" />

Blazor

Use the web component directly in Blazor pages. The IIFE build auto-registers the element.

<!-- In _Host.cshtml or index.html -->
<script src="https://unpkg.com/@@imazen/crop-image/dist/crop-image.iife.js"></script>

<!-- In a Blazor component -->
<crop-image src="@ImageUrl" aspect-ratio="16/9" adapter="imageflow"
  @ref="cropElement"></crop-image>

<button @onclick="Save">Save</button>

@@code {
  private ElementReference cropElement;

  private async Task Save() {
    // Read the querystring via JS interop
    var qs = await JS.InvokeAsync<string>(
      "eval", "document.querySelector('crop-image').riapiQuerystring");
    // Store qs alongside the image reference
  }
}

Storing Crop Data

The RIAPI querystring is a compact, URL-safe string that fully describes the crop. Store it alongside the image path in your database:

// C# / Entity Framework
public class Photo {
    public string Path { get; set; }
    public string CropParams { get; set; }  // e.g. "?crop=0.1,0.1,0.9,0.9&cropxunits=1&cropyunits=1"
}

// Render the cropped image
var url = $"/images/{photo.Path}{photo.CropParams}&width=800";

Any Backend

If you're not using Imageflow or ImageResizer, the crop-change event gives you the selection as normalized 0..1 fractions. Multiply by source dimensions to get pixel coordinates in any language:

// The selection.crop object: { x1, y1, x2, y2 } — all 0..1
// To get pixel rect:
int left   = (int)(crop.x1 * sourceWidth);
int top    = (int)(crop.y1 * sourceHeight);
int right  = (int)(crop.x2 * sourceWidth);
int bottom = (int)(crop.y2 * sourceHeight);