Skip to content

Commit

Permalink
feat(image): add extractColors (#33)
Browse files Browse the repository at this point in the history
## Summary

<!-- Please summarize your changes. -->

<!-- Please link to any applicable information (forum posts, bug
reports, etc.). -->

a new feature in `@fepack/image` which allows users to extract a palette
of colors from a given image URL. The extraction can be customized based
on user-defined parameters such as the number of colors to extract and
the quality of extraction.


![code](https://github.com/fepack/image/assets/77133565/0038c307-971d-417b-8f1e-df54b4957a83)

<img width="789" alt="스크린샷 2023-09-22 오전 12 56 18"
src="https://github.com/fepack/image/assets/77133565/24d2fa38-1749-4b51-8f4e-0b1ca36a204b">



## Checks

<!-- For completed items, change [ ] to [x]. -->

<!-- If you leave this checklist empty, your PR will very likely be
closed. -->

Please check the following:

- [x] I have written documents and tests, if needed.

---------

Co-authored-by: Jonghyeon Ko <[email protected]>
  • Loading branch information
tooooo1 and manudeli authored Sep 24, 2023
1 parent 5b15702 commit ac8c888
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/fuzzy-impalas-applaud.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fepack/image": minor
---

feat: add add extractRGBAs
4 changes: 4 additions & 0 deletions configs/eslint-config-js/noimport.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@ module.exports = {
extends: ["plugin:vitest/recommended"],
},
],
rules: {
"arrow-body-style": ["warn", "as-needed"],
"newline-before-return": "warn",
},
};
28 changes: 28 additions & 0 deletions packages/image/src/extractRGBAs.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { describe, expect, it } from "vitest";
import { extractRGBAs } from ".";

const load = (src: HTMLImageElement["src"]) =>
new Promise<HTMLImageElement>((resolve, reject) => {
const image = new Image();
image.onload = () => resolve(image);
image.onerror = () => reject();
image.src = src;
});

describe("extractRGBAs", () => {
it("should return an array of colors", async () => {
const image = await load("src/images/test.png");
const result = extractRGBAs(image, { quality: 10 });
expect(result.length).toBeGreaterThan(0);
});

it("should throw an error if quality is out of range", async () => {
const image = await load("src/images/test.png");
expect(() => extractRGBAs(image, { quality: 101 })).toThrow(
"options.quality should be between 1 and 100",
);
expect(() => extractRGBAs(image, { quality: 0 })).toThrow(
"options.quality should be between 1 and 100",
);
});
});
61 changes: 61 additions & 0 deletions packages/image/src/extractRGBAs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
type RGBA = readonly [number, number, number, number];
type ExtractRGBAsOptions = { quality: number };
const initialOptions: ExtractRGBAsOptions = { quality: 100 };
/**
* Filters and extracts relevant pixels from image data based on quality setting.
*/
export const extractRGBAs = (
/**
* The image pixel data.
*/
image: HTMLImageElement,
/**
* Quality setting for filtering pixels. Higher values mean more pixels are processed. Range: [1, 100]
*/
options = initialOptions,
) => {
if (options.quality < 1 || options.quality > 100) {
throw new Error("options.quality should be between 1 and 100");
}
const uint8ClampedArray = extractUint8ClampedArray(image);
const step = Math.ceil(100 / options.quality);

return Array.from({
length: Math.ceil(uint8ClampedArray.length / (4 * step)),
}).map((_, index) => {
const rIndex = index * 4 * step;

return [
uint8ClampedArray[rIndex],
uint8ClampedArray[rIndex + 1],
uint8ClampedArray[rIndex + 2],
uint8ClampedArray[rIndex + 3],
] as RGBA;
});
};

/**
* Extracts pixel data from an image.
*/
const extractUint8ClampedArray = (
/**
* The image element
*/
image: HTMLImageElement,
) => {
const canvas = document.createElement("canvas");
const canvasRenderingContext2D = canvas.getContext("2d");
if (!canvasRenderingContext2D) {
throw new Error("canvasRenderingContext2D is not supported");
}
canvas.width = image.naturalWidth;
canvas.height = image.naturalHeight;
canvasRenderingContext2D.drawImage(image, 0, 0);

return canvasRenderingContext2D.getImageData(
0,
0,
canvas.width,
canvas.height,
).data;
};
1 change: 1 addition & 0 deletions packages/image/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { checkWebPSupport } from "./checkWebPSupport";
export { detect } from "./detect";
export { extractRGBAs } from "./extractRGBAs";
export { load, type ImageSource } from "./load";

0 comments on commit ac8c888

Please sign in to comment.