Skip to content

Commit

Permalink
feat: reverse trimColor() result and fix default
Browse files Browse the repository at this point in the history
BREAKING CHANGE
  • Loading branch information
duniul committed Mar 27, 2023
1 parent a16e3f2 commit 4552f0d
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 47 deletions.
25 changes: 23 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
## [1.0.2](https://github.com/duniul/trim-image-data/compare/v1.0.1...v1.0.2) (2020-09-04)
## 2.0.0

### Major Changes

- BREAKING CHANGE: `trimColor` now accepts an RGBA array instead of an object.

- Before: `trimColor: ({ red, blue, green, alpha }) => { ... }`
- Now: `trimColor: ([red, blue, green, alpha]) => { ... }`

- BREAKING CHANGE: the response of `trimColor` should now return return true if you _want_ the color
to be trimmed, so essentially the opposite of the previous behavior.

### Minor Changes

- `trimColor` now also accepts a single RGBA-array as a shorthand for a callback that returns `true`
if all channels are equal to the corresponding value in the array.
- Example: `trimColor: [255, 255, 255, 255]` (would trim white pixels)

### Patch Changes

- Fix default behavior of `trimImageData()`.

## 1.0.2

### Bug Fixes

* only include built files in the package ([ca9d7cc](https://github.com/duniul/trim-image-data/commit/ca9d7cca67c1d614983fe0b495f9945baa706ca0))
- Only include built files in the package.
68 changes: 42 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,46 @@ specifying custom colors to trim.

https://trim-image-data.netlify.app

## Usage

```js
import trimImageData from 'trim-image-data';

// trim surrounding fully transparent pixels
const trimmedTransparent = trimImageData(imageData);

// trim surrounding white pixels
const trimmedWhite = trimImageData(imageData, {
trimColor: ([red, green, blue, alpha]) => {
return red === 255 && green === 255 && blue === 255 && alpha === 255;
};
});

// supports passing a RGBA array instead of a callback too
const trimmedWhite = trimImageData(imageData, {
trimColor: [255, 255, 255, 255]
});

// trim any pixel with max alpha
const trimmedDim = trimImageData(imageData, {
trimColor: ([alpha]) => alpha === 255
});
```

## Installation

| npm | yarn |
| ----------------------------- | --------------------------- |
| `npm install trim-image-data` | `yarn add trim-image-data`  |
```sh
# npm
npm install trim-image-data

## Usage
# yarn
yarn add trim-image-data

# pnpm
pnpm add trim-image-data
```

## API

### `trimImageData(imageData, trimOptions)`

Expand All @@ -29,35 +62,18 @@ not mutate the recieved instance.
- `imageData` - the ImageData-instance instance to crop

- `cropOptions` - optional, an object specifying the amount of pixels to crop from each side
- `trimColor({ red, green, blue, alpha }) => boolean`

- `trimColor([red, green, blue, alpha]) => boolean` | `trimColor: [r, g, b, a]`
Callback function used to determine if a value should be trimmed or not. Receives an object of
RGBA channels and should return a boolean.

Also accepts a single RGBA-array as a shorthand for a callback that returns `true` if all
channels are equal to the corresponding channel in the array.

**Return value:**

A new, trimmed ImageData-instance.

**Examples:**

```js
import trimImageData from 'trim-image-data';

// trim surrounding fully transparent pixels
const trimmedTransparent = trimImageData(imageData);

// trim surrounding white pixels
const trimmedWhite = trimImageData(imageData, {
trimColor: ({ red, green, blue, alpha }) => {
return red === 255 && green === 255 && blue === 255 && alpha === 255;
};
});

// trim any pixel without max alpha
const trimmedDim = trimImageData(imageData, {
trimColor: ({ alpha }) => alpha === 255
});
```

## Related packages

- [crop-image-data] - crops ImageData by specified number of pixels. Used as a dependency in
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "trim-image-data",
"version": "1.0.2",
"version": "2.0.0",
"description": "Function for trimming surrounding pixels of an ImageData-instance.",
"author": "Daniel Grefberg <[email protected]>",
"license": "ISC",
Expand Down
16 changes: 10 additions & 6 deletions src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import ImageData from '@canvas/image-data';
import trimImageData from '.';
import trimImageData, { RGBA } from '.';
import { getTestImageData } from '../test/testImageData';

global.ImageData = ImageData;
Expand Down Expand Up @@ -48,7 +48,14 @@ describe('test data arrays', () => {
expect(isSameData(result.data, expectedDataOut)).toBe(true);
});

it('trims custom pixels through trimColor function', () => {
it.each([
{
label: 'callback',
trimColor: ([red, green, blue, alpha]: RGBA) =>
red === 255 && green === 255 && blue === 255 && alpha === 255,
},
{ label: 'shorthand', trimColor: [255, 255, 255, 255] as [number, number, number, number] },
])('trims custom pixels through trimColor function ($label)', ({ trimColor }) => {
// prettier-ignore
const dataIn = flat([
b, b, b, b, b, b,
Expand All @@ -68,10 +75,7 @@ describe('test data arrays', () => {
]);

const imageData = new ImageData(Uint8ClampedArray.from(dataIn), 6, 6);
const result = trimImageData(imageData, {
trimColor: ({ red, green, blue, alpha }) =>
red === 0 && green === 0 && blue === 0 && alpha === 255,
});
const result = trimImageData(imageData, { trimColor });

expect(result.width).toEqual(4);
expect(result.height).toEqual(4);
Expand Down
32 changes: 20 additions & 12 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,35 @@ import cropImageData, { CropOptions, ImageDataLike } from 'crop-image-data';

type ImageDataLikeData = Uint8ClampedArray | number[];

export interface RGBA {
red: number;
green: number;
blue: number;
alpha: number;
}
export type RGBA = [number, number, number, number];

export type TrimColorFunc = (rgba: RGBA) => boolean;

type Side = 'top' | 'right' | 'bottom' | 'left';

export interface TrimOptions {
trimColor: TrimColorFunc;
trimColor: TrimColorFunc | RGBA;
}

function getEmptyImageData() {
return new ImageData(1, 1);
}

function getRGBA(data: ImageDataLikeData, i: number): RGBA {
return { red: data[i], green: data[i + 1], blue: data[i + 2], alpha: data[i + 3] };
function getRgba(data: ImageDataLikeData, i: number): RGBA {
return [data[i], data[i + 1], data[i + 2], data[i + 3]];
}

function getTrimColorFunc(option: TrimOptions['trimColor'] | undefined): TrimColorFunc {
if (typeof option === 'function') {
return option;
} else if (Array.isArray(option)) {
return ([r, g, b, a]) => {
return r === option[0] && g === option[1] && b === option[2] && a === option[3];
};
}

// trim transparent pixels by default
return rgba => rgba[3] === 0;
}

function scanSide(imageData: ImageDataLike, side: Side, trimColor: TrimColorFunc) {
Expand All @@ -39,8 +47,8 @@ function scanSide(imageData: ImageDataLike, side: Side, trimColor: TrimColorFunc
// loop through each row
for (let s = 0; s < secondaryAxis; s++) {
const index = (horizontal ? width * s + p : width * p + s) * 4;
const rgba = getRGBA(data, index);
if (trimColor(rgba)) {
const rgba = getRgba(data, index);
if (!trimColor(rgba)) {
// return number of columns from edge
return reverse ? start - p : p;
}
Expand All @@ -55,7 +63,7 @@ export default function trimImageData(
imageData: ImageDataLike,
trimOptions?: TrimOptions
): ImageData {
const trimColor: TrimColorFunc = trimOptions?.trimColor || (({ alpha }) => !!alpha);
const trimColor: TrimColorFunc = getTrimColorFunc(trimOptions?.trimColor);

const cropOptions: CropOptions = {};
const sides: Side[] = ['top', 'bottom', 'left', 'right'];
Expand Down

0 comments on commit 4552f0d

Please sign in to comment.