# Check for Pixel Changes Outside an Edit Mask in Node.js

After a local sleeve correction, check whether pixels outside the sleeve also changed. This Node.js example compares two aligned rasters and reports changes in the area marked as protected. It is suited to local edits that preserve the original pose, framing and pixel grid.

## 1\. Prepare two aligned rasters and a mask

Decode both images to matching eight-bit RGBA, with the same dimensions, orientation, color space and alpha representation. Supply a row-major mask with one byte per pixel. Zero protects a pixel. Any nonzero value allows the edit at that pixel.

The check compares stored channels. It skips pixels that are fully transparent in both rasters, since their hidden RGB cannot affect the result when composited. An opacity change that makes a pixel visible remains eligible for inspection.

## 2\. Measure the changes outside the mask

Save the function as check-mask.mjs. For each protected, visible pixel, it finds the largest absolute difference across the four channels. A pixel counts as changed when that difference exceeds the tolerance.

```javascript
export function checkOutsideMask({
  width, height, before, after, editable, tolerance = 0,
}) {
  const count = width * height;
  if (![width, height].every((n) => Number.isSafeInteger(n) && n > 0) ||
      !Number.isSafeInteger(count * 4)) {
    throw new Error("Expected positive raster dimensions");
  }
  if (![before, after, editable].every((a) => a instanceof Uint8Array) ||
      before.length !== count * 4 || after.length !== count * 4 ||
      editable.length !== count) {
    throw new Error("Expected two RGBA arrays and one mask byte per pixel");
  }
  if (!Number.isInteger(tolerance) || tolerance < 0 || tolerance > 255) {
    throw new Error("Expected a byte tolerance");
  }

  let inspected = 0;
  let changed = 0;
  let maxDelta = 0;
  let left = width, top = height, right = 0, bottom = 0;

  for (let pixel = 0; pixel < count; pixel++) {
    const i = pixel * 4;
    if (editable[pixel] !== 0 || (before[i + 3] === 0 && after[i + 3] === 0)) continue;
    inspected++;
    const delta = Math.max(
      Math.abs(before[i] - after[i]),
      Math.abs(before[i + 1] - after[i + 1]),
      Math.abs(before[i + 2] - after[i + 2]),
      Math.abs(before[i + 3] - after[i + 3]),
    );
    maxDelta = Math.max(maxDelta, delta);
    if (delta <= tolerance) continue;

    changed++;
    const x = pixel % width;
    const y = Math.floor(pixel / width);
    left = Math.min(left, x);
    top = Math.min(top, y);
    right = Math.max(right, x + 1);
    bottom = Math.max(bottom, y + 1);
  }
  return {
    inspected, changed, maxDelta,
    bounds: changed ? { left, top, right, bottom } : null,
  };
}
```

The largest difference is reported even when every protected pixel falls within tolerance. Bounds use left and top inclusive, right and bottom exclusive coordinates. A null rectangle means no inspected pixel exceeded the threshold.

## 3\. Make a fixture that explains the report

Save this three-by-two fixture as fixture.mjs. The top-center pixel is editable, and the bottom-left pixel is fully transparent before and after. One protected pixel changes by exactly two and stays within tolerance.

```javascript
export const fixture = {
  width: 3, height: 2, tolerance: 2,
  editable: Uint8Array.of(0, 1, 0, 0, 0, 0),
  before: Uint8Array.of(
    40,40,40,255, 50,50,50,255, 60,60,60,255,
    0,0,0,0, 80,80,80,255, 90,90,90,255,
  ),
  after: Uint8Array.of(
    40,40,40,255, 220,50,50,255, 62,60,60,255,
    90,80,70,0, 80,80,80,250, 90,90,104,255,
  ),
};
```

## 4\. Run the example

Save this wrapper as demo.mjs and run node demo.mjs. It reports four inspected pixels and two changed pixels. Both changes are on the bottom row. One comes from opacity and the other from a blue-channel shift.

```javascript
import { checkOutsideMask } from "./check-mask.mjs";
import { fixture } from "./fixture.mjs";

console.log(JSON.stringify(checkOutsideMask(fixture), null, 2));
```

## 5\. Test the mask and threshold boundaries

Save these assertions as test.mjs and run node test.mjs. The checks cover the exact tolerance boundary, an all-editable mask, a pixel becoming visible and input validation.

```javascript
import assert from "node:assert/strict";
import { checkOutsideMask } from "./check-mask.mjs";
import { fixture } from "./fixture.mjs";

const run = (changes = {}) => checkOutsideMask({ ...fixture, ...changes });

assert.deepEqual(run(), {
  inspected: 4, changed: 2, maxDelta: 14,
  bounds: { left: 1, top: 1, right: 3, bottom: 2 },
});
assert.equal(run({ tolerance: 1 }).changed, 3);
assert.deepEqual(run({ tolerance: 14 }).bounds, null);
assert.equal(run({ tolerance: 14 }).maxDelta, 14);
assert.equal(run({ after: fixture.before }).changed, 0);
assert.deepEqual(run({ editable: new Uint8Array(6).fill(1) }), {
  inspected: 0, changed: 0, maxDelta: 0, bounds: null,
});
const visible = fixture.after.slice();
visible[15] = 1;
assert.equal(run({ after: visible }).inspected, 5);
assert.throws(() => run({ tolerance: -1 }), /tolerance/);
assert.throws(() => run({ tolerance: 1.5 }), /tolerance/);
assert.throws(() => run({ width: 0 }), /dimensions/);
assert.throws(() => run({ editable: new Uint8Array(5) }), /RGBA arrays/);
assert.throws(() => run({ before: [...fixture.before] }), /RGBA arrays/);
console.log("12 checks passed");
```

## 6\. Review the flagged area

Use the bounds to zoom into the protected region. Check alignment and decode settings before interpreting a large changed area. If the whole try-on was regenerated and the pose moved, compare garment details visually instead of expecting pixel equality.

The example was tested in Node.js 24.20.0 with synthetic rasters. The tests cover channel comparisons and bounds. They did not decode images or run a garment edit.

AI assistance was used in drafting and editing this article.

Source workflow. [How to Use AI Virtual Try-On](https://www.pixmind.io/posts/how-to-use-ai-virtual-try-on)

Technical reference. [W3C PNG alpha representation](https://www.w3.org/TR/png-3/#6AlphaRepresentation)
