Online image editing with crop, resize, rotate, flip, undo/redo, and 30+ filters.
<iframe id="pe-demo" src="/online/webapp/photo-editor" width="100%" height="500" frameborder="0" style="border:1px solid #ccc; border-radius:4px;"></iframe>
<script>window._wsConnect('pe-demo', 'peSocket');</script>
<iframe src="https://sgapps.io/online/webapp/photo-editor"
width="100%" height="600" frameborder="0"></iframe>URL pattern: /online/webapp/photo-editor/url/{base64} where {base64} = btoa(imageUrl).
<iframe src="https://sgapps.io/online/webapp/photo-editor/url/aHR0cHM6Ly9leGFtcGxlLmNvbS9waG90by5qcGc="
width="100%" height="600" frameborder="0"></iframe>var socket = new WindowSocket(iframe.contentWindow);
socket.start();
socket.fire("webapp::instance::request", eventName, ...args, callback);reset -- Clear CanvasClears the editor to a blank state, removing all image data.
socket.fire("webapp::instance::request", "reset", function (err) {
console.log("Canvas cleared");
});setDataBase64 -- Load Image from Base64Loads an image from a base64-encoded data URL. Use this to programmatically set the image content from your application -- for example after capturing from a canvas or receiving from an API.
| Arg | Type | Description |
|---|---|---|
base64Url | string | Full data URL (e.g., "data:image/png;base64,...") |
callback | function | (err) |
socket.fire("webapp::instance::request", "setDataBase64",
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...",
function (err) { console.log("Image loaded into editor"); }
);getDataBase64 -- Export ImageExports the current canvas as a base64 data URL. This is the primary way to get the edited image back into your application -- you can send it to a server, display it in an <img> tag, or trigger a download.
| Arg | Type | Description |
|---|---|---|
type | string | Output MIME type: "image/png", "image/jpeg", "image/webp" |
callback | function | (err, dataUrl: string) |
quality | number | 0-1 quality for JPEG/WebP (optional, default 1) |
socket.fire("webapp::instance::request", "getDataBase64", "image/png",
function (err, dataUrl) {
document.getElementById('preview').src = dataUrl;
}
);<button onclick="window._ws('peSocket')&&window.peSocket.fire('webapp::instance::request','getDataBase64','image/png',function(e,d){if(d){var i=document.createElement('img');i.src=d;i.style.maxHeight='120px';document.getElementById('pe-export-result').innerHTML='';document.getElementById('pe-export-result').appendChild(i);}})">Try: Export PNG from demo above</button><div id="pe-export-result" style="margin:8px 0; min-height:20px;"></div>
loadFromURL -- Load Image from URLLoads an image directly from a URL without base64 encoding. The URL must be accessible from the browser (same-origin or CORS-enabled).
| Arg | Type | Description |
|---|---|---|
url | string | Image URL |
callback | function | (err) |
socket.fire("webapp::instance::request", "loadFromURL",
"https://example.com/photo.jpg",
function (err) { console.log(err || "Loaded"); }
);resize -- Resize ImageResizes the entire canvas to new dimensions. The image content is scaled to fit.
| Arg | Type | Description |
|---|---|---|
width | number | New width in pixels |
height | number | New height in pixels |
callback | function | (err) |
socket.fire("webapp::instance::request", "resize", 800, 600, function (err) {
console.log("Resized to 800x600");
});crop -- Crop ImageCrops the image to a rectangle defined by position and size. Pixels outside the rectangle are discarded.
| Arg | Type | Description |
|---|---|---|
x | number | Left offset in pixels |
y | number | Top offset in pixels |
width | number | Crop width |
height | number | Crop height |
callback | function | (err) |
socket.fire("webapp::instance::request", "crop", 50, 50, 300, 200, function (err) {
console.log("Cropped to 300x200 starting at (50,50)");
});flipH, flipW -- Flip ImageMirror the image along the horizontal axis (flipH) or vertical axis (flipW). Useful for correcting selfie-mode camera captures or creating mirror effects.
socket.fire("webapp::instance::request", "flipH", function () {});
socket.fire("webapp::instance::request", "flipW", function () {});<button onclick="window._ws('peSocket')&&window.peSocket.fire('webapp::instance::request','flipH')">Try: Flip Horizontal</button><button onclick="window._ws('peSocket')&&window.peSocket.fire('webapp::instance::request','flipW')">Try: Flip Vertical</button>
rotateCW, rotateCCW -- Rotate ImageRotate the image 90 degrees clockwise (rotateCW) or counter-clockwise (rotateCCW). Canvas dimensions are swapped (width becomes height and vice versa).
socket.fire("webapp::instance::request", "rotateCW", function () {});
socket.fire("webapp::instance::request", "rotateCCW", function () {});<button onclick="window._ws('peSocket')&&window.peSocket.fire('webapp::instance::request','rotateCW')">Try: Rotate CW</button><button onclick="window._ws('peSocket')&&window.peSocket.fire('webapp::instance::request','rotateCCW')">Try: Rotate CCW</button>
zoom -- Set Zoom LevelSets the editor's display zoom level. This doesn't change the actual image data -- only how it's displayed.
| Arg | Type | Description |
|---|---|---|
level | number | 1 = 100%, 2 = 200%, 0.5 = 50% |
callback | function | (err) |
socket.fire("webapp::instance::request", "zoom", 2, function () {
console.log("Zoomed to 200%");
});getScale -- Get Current ZoomReturns the current zoom scale factor.
socket.fire("webapp::instance::request", "getScale", function (err, scale) {
console.log("Current zoom:", Math.round(scale * 100) + "%");
});fitToView -- Fit Image to ViewportAutomatically scales and centers the image so the entire canvas is visible within the editor viewport.
socket.fire("webapp::instance::request", "fitToView", function () {
console.log("Image fitted to viewport");
});<button onclick="window._ws('peSocket')&&window.peSocket.fire('webapp::instance::request','fitToView')">Try: Fit to View</button><button onclick="window._ws('peSocket')&&window.peSocket.fire('webapp::instance::request','getScale',function(e,s){alert('Zoom: '+Math.round(s*100)+'%')})">Try: Get Scale</button>
getImageInfo -- Get Image MetadataReturns dimensions, layer count, and current scale of the image being edited. Useful for displaying status information or making decisions about which operations to apply.
socket.fire("webapp::instance::request", "getImageInfo", function (err, info) {
console.log(info);
// { width: 1920, height: 1080, layerCount: 1, scale: 1 }
});<button onclick="window._ws('peSocket')&&window.peSocket.fire('webapp::instance::request','getImageInfo',function(e,i){alert(JSON.stringify(i,null,2))})">Try: Get Image Info</button>
history:undo, history:redo, history:save -- Undo/RedoThe editor maintains a history stack of image states. Use history:save to create a checkpoint before making changes, then history:undo and history:redo to navigate the stack.
// Save a checkpoint, apply a filter, then undo it
socket.fire("webapp::instance::request", "history:save", function () {
socket.fire("webapp::instance::request", "applyFilters",
[{ name: "sepia", params: { level: 0.9 } }],
function () { console.log("Applied -- press Undo to revert"); }
);
});socket.fire("webapp::instance::request", "history:undo"); // go back
socket.fire("webapp::instance::request", "history:redo"); // go forward<button onclick="window._ws('peSocket')&&window.peSocket.fire('webapp::instance::request','history:save',function(){window.peSocket.fire('webapp::instance::request','applyFilters',[{name:'sepia',params:{level:0.9}}])})">Try: Save + Sepia</button><button onclick="window._ws('peSocket')&&window.peSocket.fire('webapp::instance::request','history:undo')">Try: Undo</button><button onclick="window._ws('peSocket')&&window.peSocket.fire('webapp::instance::request','history:redo')">Try: Redo</button>
applyFilters -- Apply Image FiltersApply one or more image filters in sequence. Filters are processed one after another on each layer. The editor includes 30+ filters ranging from basic adjustments to artistic effects and social media presets.
| Arg | Type | Description |
|---|---|---|
filters | Array | Array of { name: string, params: object } |
callback | function | (err) |
// Apply a single filter
socket.fire("webapp::instance::request", "applyFilters", [
{ name: "brightness", params: { level: 0.3 } }
]);
// Chain multiple filters
socket.fire("webapp::instance::request", "applyFilters", [
{ name: "brightness", params: { level: 0.1 } },
{ name: "contrast", params: { level: 0.5 } },
{ name: "sepia", params: { level: 0.3 } }
], function () { console.log("Vintage look applied"); });<button onclick="window._ws('peSocket')&&window.peSocket.fire('webapp::instance::request','applyFilters',[{name:'sepia',params:{level:0.9}}])">Try: Sepia</button><button onclick="window._ws('peSocket')&&window.peSocket.fire('webapp::instance::request','applyFilters',[{name:'invert',params:{level:1}}])">Try: Invert</button><button onclick="window._ws('peSocket')&&window.peSocket.fire('webapp::instance::request','applyFilters',[{name:'blur-stack',params:{level:5}}])">Try: Blur</button><button onclick="window._ws('peSocket')&&window.peSocket.fire('webapp::instance::request','applyFilters',[{name:'edge',params:{level:1}}])">Try: Edge Detect</button><button onclick="window._ws('peSocket')&&window.peSocket.fire('webapp::instance::request','applyFilters',[{name:'mosaic',params:{level:15}}])">Try: Mosaic</button><button onclick="window.ws('peSocket')&&window.peSocket.fire('webapp::instance::request','applyFilters',[{name:'social1977',params:{}}])">Try: 1977 Preset</button>
| Filter | Parameters | Description |
|---|---|---|
brightness | level: -1 to 1 | Lighten or darken |
contrast | level: -3 to 3 | Increase/decrease contrast |
saturation | level: -1 to 3 | Color intensity (-1 = grayscale) |
sepia | level: -3 to 3 | Warm brownish tone |
vibrance | level: -300 to 300 | Smart saturation boost |
hsl-adjustment | hue: -180..180, saturation: -100..100, lightness: -100..100 | Full HSL control |
brightness-contrast-gimp | brightness: -100..100, contrast: -100..100 | GIMP-style adjustment |
brightness-contrast-photo | brightness: -100..100, contrast: -100..100 | Photo-style adjustment |
channels | channel: 1-3 | Extract channel (1=R, 2=G, 3=B) |
channels-adjust | redOffset, redMultiply, greenOffset, greenMultiply, blueOffset, blueMultiply, alphaOffset, alphaMultiply: 0-255 | Per-channel color transform |
gamma | level: 0 to 3 | Gamma correction |
gamma-v2 | level: 0.01 to 100 | Advanced gamma |
clip | level: -1 to 1 | Color value clipping |
| Filter | Parameters | Description |
|---|---|---|
grayscale | level: 0 or 1 | Convert to grayscale |
grayscale-v2 | level: 0 or 1 | Rec.601 weighted grayscale |
desaturate | level: 0 or 1 | Desaturate via luminosity |
invert | level: 0 or 1 | Negative image |
binarize | level: 0 to 1 | Black/white threshold |
posterize | levels: 2 to 255 | Reduce color levels |
solarize | level: 0 or 1 | Solarization effect |
| Filter | Parameters | Description |
|---|---|---|
blur-box | width, height: 0-100, radius: 0-100 | Box blur |
blur-gaussian | level: 1 to 4 | Gaussian blur |
blur-stack | level: 1 to 100 | Fast stack blur |
sharpen | level: 2 to 255 | Sharpen edges |
| Filter | Parameters | Description |
|---|---|---|
edge | level: 0 or 1 | Edge detection |
emboss | level: 0 or 1 | 3D emboss effect |
enrich | level: 0 or 1 | Edge enhancement |
mosaic | level: 2 to 400 | Pixelation |
oil | range: 1-200, levels: 1-20 | Oil painting |
dither | level: 2 to 255 | Floyd-Steinberg dithering |
twirl | centerX/Y: 0-1, radius: 0-1000, angle: -360..360, edge: -1..1, smooth: 0/1 | Spiral distortion |
transpose | level: 0 or 1 | Mirror X/Y coordinates |
| Filter | Description |
|---|---|
social_1977 | Warm vintage (sepia + hue shift) |
social_aden | Soft warm tone (sepia + saturation + lightness) |
<!DOCTYPE html>
<html>
<head><title>My Image Editor Widget</title></head>
<body>
<div style="display:flex;gap:8px;padding:10px;flex-wrap:wrap;">
<button id="btn-sepia">Sepia</button>
<button id="btn-undo">Undo</button>
<button id="btn-fit">Fit to View</button>
<button id="btn-export">Export PNG</button>
<button id="btn-info">Image Info</button>
</div>
<iframe id="editor" src="https://sgapps.io/online/webapp/photo-editor"
width="100%" height="500" frameborder="0"></iframe>
<img id="preview" style="max-width:300px;margin:10px;">
<pre id="info-box" style="background:#f6f8fa;padding:8px;"></pre>
<script src="https://sgapps.io/components/window-socket/index.js"></script>
<script>
var socket = new WindowSocket(document.getElementById('editor').contentWindow);
socket.start();
socket.on("webapp::connection::ping", function () {
socket.fire("webapp::instance::embed-mode", true);
});
document.getElementById('btn-sepia').onclick = function () {
socket.fire("webapp::instance::request", "history:save");
socket.fire("webapp::instance::request", "applyFilters",
[{ name: "sepia", params: { level: 0.9 } }]);
};
document.getElementById('btn-undo').onclick = function () {
socket.fire("webapp::instance::request", "history:undo");
};
document.getElementById('btn-fit').onclick = function () {
socket.fire("webapp::instance::request", "fitToView");
};
document.getElementById('btn-export').onclick = function () {
socket.fire("webapp::instance::request", "getDataBase64", "image/png",
function (err, d) { document.getElementById('preview').src = d; });
};
document.getElementById('btn-info').onclick = function () {
socket.fire("webapp::instance::request", "getImageInfo",
function (err, info) {
document.getElementById('info-box').textContent = JSON.stringify(info, null, 2);
});
};
</script>
</body>
</html>