Apply a sepia filter to image RGB pixel samples
Pure red, 255, 0, 0, becomes the warm brown 100, 89, 69 with the standard sepia matrix, and every channel is rounded and kept within 0 to 255. Enter decoded RGB pixel values and get their sepia versions back in the same order. Free in your browser.
- Free
- Runs in your browser
- No sign-up
- Your data stays on your device
Run — free
Apply a sepia filter to decoded image pixels without uploading a file or relying on an image codec. Provide an ordered array of RGB samples and receive corresponding warm brown-toned samples calculated with the standard sepia transform matrix. Every transformed channel is rounded and clamped to the valid zero-to-255 range. Complete channel validation prevents incomplete or malformed pixels from quietly changing the result, while deterministic processing makes the converter suitable for production pipelines, browser checks, teaching examples, and exact automated tests.
Prepare complete RGB pixel samples
Place decoded pixels in the samples field as a nonempty array of objects. Every object must contain the channel names r, g, and b, and every channel must be a finite numeric value between zero and 255 inclusive. Integers are conventional, but fractional input is accepted because some image pipelines produce intermediate floating-point samples before final encoding.
The converter checks every record rather than assuming that a missing component means zero. Keep samples in raster order if you intend to rebuild an image afterward, because output order exactly matches input order. Split larger rasters into fixed-size chunks, submit them in a controlled order, and concatenate returned sample arrays in that same order.
The capability works with RGB samples, not PNG, JPEG, AVIF, WebP, or other encoded bytes. Decode a source file first and preserve width, height, row stride, orientation, and any alpha channel separately so those values can be combined with the transformed samples during encoding.
Understand the standard sepia transform
Each output pixel comes from three weighted sums. Red is calculated as 0.393 times r plus 0.769 times g plus 0.189 times b. Green uses 0.349 times r plus 0.686 times g plus 0.168 times b.
Blue uses 0.272 times r plus 0.534 times g plus 0.131 times b. This familiar sepia matrix changes both luminance and channel balance, producing highlights dominated by warm red and green while reducing blue. The converter rounds each weighted sum to the nearest whole number and then clamps it to the inclusive channel range from zero through 255.
Clamping matters because the rows of the matrix can yield values above 255 for bright input pixels. Without it, downstream byte buffers could wrap, reject the value, or display an unintended color. Processing is deterministic and pure: it does not use a network service, trained model, random source, current time, hidden color profile, or mutable request cache.
Integrate and verify the converted result
Read the returned samples array as a positional replacement for the submitted RGB array. The count field should equal the number of input records; compare it before placing samples back into a tile, scanline, thumbnail, canvas buffer, or full raster. If the original image has transparency, keep its alpha values in a parallel buffer and restore them when encoding, because this contract intentionally transforms only red, green, and blue.
Bright white is especially useful for verifying that all three computed values clamp to 255 instead of exceeding the byte range. Pure colors reveal accidental coefficient or channel-order changes, and neutral values show the characteristic warm separation between returned channels. Treat validation failures as problems with decoding or request construction rather than transient failures to retry unchanged.
A missing-channel message means the source buffer or mapping logic must be corrected. A range or finite-number error commonly indicates an unnormalized HDR value, NaN from earlier arithmetic, or a typed-buffer conversion mistake. Correct such inputs deliberately before resubmitting so the sepia output remains reproducible.
What you can do with it
Build sepia image buffers
Transform decoded RGB pixels into warm sepia samples, then restore the original raster dimensions and encode the result.
FAQ
Which sepia matrix is used?
The converter uses rows [0.393, 0.769, 0.189], [0.349, 0.686, 0.168], and [0.272, 0.534, 0.131] for output red, green, and blue.
What happens when a transformed channel exceeds 255?
Each weighted result is rounded to the nearest integer and clamped to the valid inclusive range from zero through 255.
Does this capability accept encoded image files or preserve alpha?
No. It accepts decoded RGB records only. Keep alpha and image dimensions separately, then encode the returned samples yourself.
What color does pure white become in sepia?
It becomes 255, 255, 239, a warm cream. The red and green sums go above 255 and are held at the maximum, while blue drops a little, which is what gives sepia highlights their warmth.
What happens if a pixel is missing one of its channels?
The whole conversion stops and names the position and the missing channel. Skipping that pixel would shift every later one, and filling in a value would invent a color you never supplied.
Related tools
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/image/sepia-convert \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"samples":[{"r":255,"g":0,"b":0},{"r":30,"g":120,"b":220}]}'const res = await fetch("https://api.kit.forhosting.com/image/sepia-convert", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"samples": [
{
"r": 255,
"g": 0,
"b": 0
},
{
"r": 30,
"g": 120,
"b": 220
}
]
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/image/sepia-convert",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"samples": [
{
"r": 255,
"g": 0,
"b": 0
},
{
"r": 30,
"g": 120,
"b": 220
}
]
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/image/sepia-convert", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"samples":[{"r":255,"g":0,"b":0},{"r":30,"g":120,"b":220}]}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"samples":[{"r":255,"g":0,"b":0},{"r":30,"g":120,"b":220}]}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/image/sepia-convert", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"samples": [
{
"r": 255,
"g": 0,
"b": 0
},
{
"r": 30,
"g": 120,
"b": 220
}
]
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "image.sepia_convert",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Limits
max_samples | 100000 |
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |
Technical notes
Prepare complete RGB pixel samples
If sample 4 lacks b, for example, the entire request fails with an invalid_input error identifying that zero-based index and missing channel. This strict behavior protects image structure: silently skipping a record would shift every later pixel, while substituting a value would invent color information that the caller never supplied. The endpoint accepts up to one hundred thousand records in one request.
Understand the standard sepia transform
Identical JSON input therefore produces identical JSON output. This focused calculation does not linearize gamma-encoded RGB, interpret ICC profiles, simulate chemical film, add grain, preserve transparency, or blend the result with the original. Those are separate creative or color-management steps.
Integrate and verify the converted result
Browser execution is free, while each API request costs $0.002.
Integration use cases
Create exact filter fixtures
Generate deterministic expected values for tests of canvas code, image-processing libraries, and pixel conversion pipelines.
Inspect representative colors
Check a small palette in the browser before running the identical matrix transform through an automated API workflow.
Technical questions
What happens if a sample is missing a channel?
The request fails with invalid_input and identifies the zero-based sample index and the missing r, g, or b channel.
How much does an API request cost?
An API request costs $0.002. The same deterministic conversion can also run free in the browser.