From 47ae5c7202e9be723938a18bdb155c6395d74061 Mon Sep 17 00:00:00 2001 From: Habib Date: Fri, 28 Jun 2024 11:29:40 +0200 Subject: [PATCH 1/8] Create new tool --- .../convert-jgp-to-png.service.test.ts | 6 ++++++ src/pages/image/png/convert-jgp-to-png/index.tsx | 11 +++++++++++ src/pages/image/png/convert-jgp-to-png/meta.ts | 14 ++++++++++++++ src/pages/image/png/convert-jgp-to-png/service.ts | 0 src/pages/image/png/index.ts | 1 + 5 files changed, 32 insertions(+) create mode 100644 src/pages/image/png/convert-jgp-to-png/convert-jgp-to-png.service.test.ts create mode 100644 src/pages/image/png/convert-jgp-to-png/index.tsx create mode 100644 src/pages/image/png/convert-jgp-to-png/meta.ts create mode 100644 src/pages/image/png/convert-jgp-to-png/service.ts diff --git a/src/pages/image/png/convert-jgp-to-png/convert-jgp-to-png.service.test.ts b/src/pages/image/png/convert-jgp-to-png/convert-jgp-to-png.service.test.ts new file mode 100644 index 0000000..d60631b --- /dev/null +++ b/src/pages/image/png/convert-jgp-to-png/convert-jgp-to-png.service.test.ts @@ -0,0 +1,6 @@ +import { expect, describe, it } from 'vitest'; +// import { } from './service'; +// +// describe('convert-jgp-to-png', () => { +// +// }) diff --git a/src/pages/image/png/convert-jgp-to-png/index.tsx b/src/pages/image/png/convert-jgp-to-png/index.tsx new file mode 100644 index 0000000..bc4a857 --- /dev/null +++ b/src/pages/image/png/convert-jgp-to-png/index.tsx @@ -0,0 +1,11 @@ +import { Box } from '@mui/material'; +import React from 'react'; +import * as Yup from 'yup'; + +const initialValues = {}; +const validationSchema = Yup.object({ + // splitSeparator: Yup.string().required('The separator is required') +}); +export default function ConvertJgpToPng() { + return Lorem ipsum; +} diff --git a/src/pages/image/png/convert-jgp-to-png/meta.ts b/src/pages/image/png/convert-jgp-to-png/meta.ts new file mode 100644 index 0000000..53e6523 --- /dev/null +++ b/src/pages/image/png/convert-jgp-to-png/meta.ts @@ -0,0 +1,14 @@ +import { defineTool } from '@tools/defineTool'; +import { lazy } from 'react'; +// import image from '@assets/text.png'; + +export const tool = defineTool('png', { + name: 'Convert jgp to png', + path: 'convert-jgp-to-png', + // image, + description: + 'Quickly your JPG images to PNG. Just import your PNG image in the editor on the left', + shortDescription: '', + keywords: ['convert', 'jgp', 'to', 'png'], + component: lazy(() => import('./index')) +}); diff --git a/src/pages/image/png/convert-jgp-to-png/service.ts b/src/pages/image/png/convert-jgp-to-png/service.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/pages/image/png/index.ts b/src/pages/image/png/index.ts index 5cda167..a95de7d 100644 --- a/src/pages/image/png/index.ts +++ b/src/pages/image/png/index.ts @@ -1,3 +1,4 @@ +import { tool as pngConvertJgpToPng } from './convert-jgp-to-png/meta'; import { tool as pngCreateTransparent } from './create-transparent/meta'; import { tool as changeColorsInPng } from './change-colors-in-png/meta'; From 8064928ba9911950de94b679f19831eb694a79a8 Mon Sep 17 00:00:00 2001 From: Habib Date: Fri, 28 Jun 2024 12:01:35 +0200 Subject: [PATCH 2/8] feat: convert jpg to png (w/o options) --- .../image/png/convert-jgp-to-png/index.tsx | 61 ++++++++++++++++++- .../image/png/convert-jgp-to-png/meta.ts | 12 ++-- src/pages/image/png/index.ts | 8 ++- 3 files changed, 71 insertions(+), 10 deletions(-) diff --git a/src/pages/image/png/convert-jgp-to-png/index.tsx b/src/pages/image/png/convert-jgp-to-png/index.tsx index bc4a857..0e7f785 100644 --- a/src/pages/image/png/convert-jgp-to-png/index.tsx +++ b/src/pages/image/png/convert-jgp-to-png/index.tsx @@ -1,5 +1,8 @@ import { Box } from '@mui/material'; -import React from 'react'; +import ToolInputAndResult from 'components/ToolInputAndResult'; +import ToolFileInput from 'components/input/ToolFileInput'; +import ToolFileResult from 'components/result/ToolFileResult'; +import React, { useState } from 'react'; import * as Yup from 'yup'; const initialValues = {}; @@ -7,5 +10,59 @@ const validationSchema = Yup.object({ // splitSeparator: Yup.string().required('The separator is required') }); export default function ConvertJgpToPng() { - return Lorem ipsum; + const [input, setInput] = useState(null); + const [result, setResult] = useState(null); + + const onFileInputChange = (file: File): void => { + if (!file) return; + setInput(file); + convertJpgToPng(file); + }; + + const convertJpgToPng = async (file: File): Promise => { + if (!file) return; + + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + if (ctx == null) return; + + const img = new Image(); + img.src = URL.createObjectURL(file); + await img.decode(); + + canvas.width = img.width; + canvas.height = img.height; + ctx.drawImage(img, 0, 0); + + canvas.toBlob((blob) => { + if (blob) { + const newFile = new File([blob], file.name, { + type: 'image/png' + }); + setResult(newFile); + } + }, 'image/png'); + }; + + return ( + + + } + result={ + + } + /> + + ); } diff --git a/src/pages/image/png/convert-jgp-to-png/meta.ts b/src/pages/image/png/convert-jgp-to-png/meta.ts index 53e6523..abcb39d 100644 --- a/src/pages/image/png/convert-jgp-to-png/meta.ts +++ b/src/pages/image/png/convert-jgp-to-png/meta.ts @@ -1,14 +1,14 @@ import { defineTool } from '@tools/defineTool'; import { lazy } from 'react'; -// import image from '@assets/text.png'; +import image from '@assets/image.png'; export const tool = defineTool('png', { - name: 'Convert jgp to png', + name: 'Convert JPG to PNG', path: 'convert-jgp-to-png', - // image, + image, description: - 'Quickly your JPG images to PNG. Just import your PNG image in the editor on the left', - shortDescription: '', - keywords: ['convert', 'jgp', 'to', 'png'], + 'Quickly convert your JPG images to PNG. Just import your PNG image in the editor on the left', + shortDescription: 'Quickly convert your JPG images to PNG', + keywords: ['convert', 'jgp', 'png'], component: lazy(() => import('./index')) }); diff --git a/src/pages/image/png/index.ts b/src/pages/image/png/index.ts index a95de7d..8a1646a 100644 --- a/src/pages/image/png/index.ts +++ b/src/pages/image/png/index.ts @@ -1,5 +1,9 @@ -import { tool as pngConvertJgpToPng } from './convert-jgp-to-png/meta'; +import { tool as convertJgpToPng } from './convert-jgp-to-png/meta'; import { tool as pngCreateTransparent } from './create-transparent/meta'; import { tool as changeColorsInPng } from './change-colors-in-png/meta'; -export const pngTools = [changeColorsInPng, pngCreateTransparent]; +export const pngTools = [ + changeColorsInPng, + pngCreateTransparent, + convertJgpToPng +]; From d1976c5dee47c7594d2d26b9e08dfb2d9ac6a189 Mon Sep 17 00:00:00 2001 From: Habib Date: Fri, 28 Jun 2024 12:47:28 +0200 Subject: [PATCH 3/8] Add transparency options --- .../image/png/convert-jgp-to-png/index.tsx | 150 +++++++++++++++--- 1 file changed, 125 insertions(+), 25 deletions(-) diff --git a/src/pages/image/png/convert-jgp-to-png/index.tsx b/src/pages/image/png/convert-jgp-to-png/index.tsx index 0e7f785..ac57cfd 100644 --- a/src/pages/image/png/convert-jgp-to-png/index.tsx +++ b/src/pages/image/png/convert-jgp-to-png/index.tsx @@ -1,11 +1,20 @@ import { Box } from '@mui/material'; import ToolInputAndResult from 'components/ToolInputAndResult'; import ToolFileInput from 'components/input/ToolFileInput'; +import CheckboxWithDesc from 'components/options/CheckboxWithDesc'; +import ColorSelector from 'components/options/ColorSelector'; +import TextFieldWithDesc from 'components/options/TextFieldWithDesc'; +import ToolOptions from 'components/options/ToolOptions'; import ToolFileResult from 'components/result/ToolFileResult'; +import Color from 'color'; import React, { useState } from 'react'; import * as Yup from 'yup'; -const initialValues = {}; +const initialValues = { + enableTransparency: false, + color: 'white', + similarity: '10' +}; const validationSchema = Yup.object({ // splitSeparator: Yup.string().required('The separator is required') }); @@ -13,35 +22,92 @@ export default function ConvertJgpToPng() { const [input, setInput] = useState(null); const [result, setResult] = useState(null); - const onFileInputChange = (file: File): void => { - if (!file) return; - setInput(file); - convertJpgToPng(file); - }; + const compute = async ( + optionsValues: typeof initialValues, + input: any + ): Promise => { + if (!input) return; + + const processImage = async ( + file: File, + transparencyTransform?: { + color: [number, number, number]; + similarity: number; + } + ) => { + const canvas = document.createElement('canvas'); + const ctx = canvas.getContext('2d'); + if (ctx == null) return; + const img = new Image(); + + img.src = URL.createObjectURL(file); + await img.decode(); + + canvas.width = img.width; + canvas.height = img.height; + ctx.drawImage(img, 0, 0); + + if (transparencyTransform) { + const { color, similarity } = transparencyTransform; - const convertJpgToPng = async (file: File): Promise => { - if (!file) return; + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + const data: Uint8ClampedArray = imageData.data; - const canvas = document.createElement('canvas'); - const ctx = canvas.getContext('2d'); - if (ctx == null) return; + const colorDistance = ( + c1: [number, number, number], + c2: [number, number, number] + ) => { + return Math.sqrt( + Math.pow(c1[0] - c2[0], 2) + + Math.pow(c1[1] - c2[1], 2) + + Math.pow(c1[2] - c2[2], 2) + ); + }; + const maxColorDistance = Math.sqrt( + Math.pow(255, 2) + Math.pow(255, 2) + Math.pow(255, 2) + ); + const similarityThreshold = (similarity / 100) * maxColorDistance; - const img = new Image(); - img.src = URL.createObjectURL(file); - await img.decode(); + for (let i = 0; i < data.length; i += 4) { + const currentColor: [number, number, number] = [ + data[i], + data[i + 1], + data[i + 2] + ]; + if (colorDistance(currentColor, color) <= similarityThreshold) { + data[i + 3] = 0; + } + } + + ctx.putImageData(imageData, 0, 0); + } - canvas.width = img.width; - canvas.height = img.height; - ctx.drawImage(img, 0, 0); + canvas.toBlob((blob) => { + if (blob) { + const newFile = new File([blob], file.name, { + type: 'image/png' + }); + setResult(newFile); + } + }, 'image/png'); + }; - canvas.toBlob((blob) => { - if (blob) { - const newFile = new File([blob], file.name, { - type: 'image/png' - }); - setResult(newFile); + if (optionsValues.enableTransparency) { + let rgb: [number, number, number]; + try { + //@ts-ignore + rgb = Color(optionsValues.color).rgb().array(); + } catch (err) { + return; } - }, 'image/png'); + + processImage(input, { + color: rgb, + similarity: Number(optionsValues.similarity) + }); + } else { + processImage(input); + } }; return ( @@ -50,7 +116,7 @@ export default function ConvertJgpToPng() { input={ @@ -63,6 +129,40 @@ export default function ConvertJgpToPng() { /> } /> + [ + { + title: 'From color and to color', + component: ( + + updateField('enableTransparency', value)} + description="Make the color below transparent." + /> + updateField('color', val)} + description={'With this color (to color)'} + /> + updateField('similarity', val)} + description={ + 'Match this % of similar. For example, 10% white will match white and a little bit of gray.' + } + /> + + ) + } + ]} + initialValues={initialValues} + input={input} + validationSchema={validationSchema} + /> ); } From 3b575af888095b4c3269d20e343364a411be3262 Mon Sep 17 00:00:00 2001 From: Habib Date: Fri, 28 Jun 2024 12:48:45 +0200 Subject: [PATCH 4/8] delete test and service file --- .../convert-jgp-to-png/convert-jgp-to-png.service.test.ts | 6 ------ src/pages/image/png/convert-jgp-to-png/service.ts | 0 2 files changed, 6 deletions(-) delete mode 100644 src/pages/image/png/convert-jgp-to-png/convert-jgp-to-png.service.test.ts delete mode 100644 src/pages/image/png/convert-jgp-to-png/service.ts diff --git a/src/pages/image/png/convert-jgp-to-png/convert-jgp-to-png.service.test.ts b/src/pages/image/png/convert-jgp-to-png/convert-jgp-to-png.service.test.ts deleted file mode 100644 index d60631b..0000000 --- a/src/pages/image/png/convert-jgp-to-png/convert-jgp-to-png.service.test.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { expect, describe, it } from 'vitest'; -// import { } from './service'; -// -// describe('convert-jgp-to-png', () => { -// -// }) diff --git a/src/pages/image/png/convert-jgp-to-png/service.ts b/src/pages/image/png/convert-jgp-to-png/service.ts deleted file mode 100644 index e69de29..0000000 From eaea6f5858935c10010629ec38bc9831c7216c49 Mon Sep 17 00:00:00 2001 From: Habib Date: Fri, 28 Jun 2024 14:26:33 +0200 Subject: [PATCH 5/8] add e2e tests --- package-lock.json | 889 +++++++++++++++++- package.json | 1 + src/components/options/ColorSelector.tsx | 14 +- .../image/png/change-colors-in-png/index.tsx | 4 +- .../convert-jgp-to-png.e2e.spec.ts | 72 ++ .../image/png/convert-jgp-to-png/index.tsx | 7 +- .../image/png/convert-jgp-to-png/test.jpg | Bin 0 -> 15149 bytes .../image/png/create-transparent/index.tsx | 2 +- 8 files changed, 973 insertions(+), 16 deletions(-) create mode 100644 src/pages/image/png/convert-jgp-to-png/convert-jgp-to-png.e2e.spec.ts create mode 100644 src/pages/image/png/convert-jgp-to-png/test.jpg diff --git a/package-lock.json b/package-lock.json index 1943824..1247a0e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@types/omggif": "^1.0.5", "color": "^4.2.3", "formik": "^2.4.6", + "jimp": "^0.22.12", "lodash": "^4.17.21", "morsee": "^1.0.9", "notistack": "^3.0.1", @@ -1509,6 +1510,399 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jimp/bmp": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.22.12.tgz", + "integrity": "sha512-aeI64HD0npropd+AR76MCcvvRaa+Qck6loCOS03CkkxGHN5/r336qTM5HPUdHKMDOGzqknuVPA8+kK1t03z12g==", + "dependencies": { + "@jimp/utils": "^0.22.12", + "bmp-js": "^0.1.0" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/core": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.22.12.tgz", + "integrity": "sha512-l0RR0dOPyzMKfjUW1uebzueFEDtCOj9fN6pyTYWWOM/VS4BciXQ1VVrJs8pO3kycGYZxncRKhCoygbNr8eEZQA==", + "dependencies": { + "@jimp/utils": "^0.22.12", + "any-base": "^1.1.0", + "buffer": "^5.2.0", + "exif-parser": "^0.1.12", + "file-type": "^16.5.4", + "isomorphic-fetch": "^3.0.0", + "pixelmatch": "^4.0.2", + "tinycolor2": "^1.6.0" + } + }, + "node_modules/@jimp/custom": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.22.12.tgz", + "integrity": "sha512-xcmww1O/JFP2MrlGUMd3Q78S3Qu6W3mYTXYuIqFq33EorgYHV/HqymHfXy9GjiCJ7OI+7lWx6nYFOzU7M4rd1Q==", + "dependencies": { + "@jimp/core": "^0.22.12" + } + }, + "node_modules/@jimp/gif": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.22.12.tgz", + "integrity": "sha512-y6BFTJgch9mbor2H234VSjd9iwAhaNf/t3US5qpYIs0TSbAvM02Fbc28IaDETj9+4YB4676sz4RcN/zwhfu1pg==", + "dependencies": { + "@jimp/utils": "^0.22.12", + "gifwrap": "^0.10.1", + "omggif": "^1.0.9" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/jpeg": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.22.12.tgz", + "integrity": "sha512-Rq26XC/uQWaQKyb/5lksCTCxXhtY01NJeBN+dQv5yNYedN0i7iYu+fXEoRsfaJ8xZzjoANH8sns7rVP4GE7d/Q==", + "dependencies": { + "@jimp/utils": "^0.22.12", + "jpeg-js": "^0.4.4" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-blit": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.22.12.tgz", + "integrity": "sha512-xslz2ZoFZOPLY8EZ4dC29m168BtDx95D6K80TzgUi8gqT7LY6CsajWO0FAxDwHz6h0eomHMfyGX0stspBrTKnQ==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-blur": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.22.12.tgz", + "integrity": "sha512-S0vJADTuh1Q9F+cXAwFPlrKWzDj2F9t/9JAbUvaaDuivpyWuImEKXVz5PUZw2NbpuSHjwssbTpOZ8F13iJX4uw==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-circle": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-0.22.12.tgz", + "integrity": "sha512-SWVXx1yiuj5jZtMijqUfvVOJBwOifFn0918ou4ftoHgegc5aHWW5dZbYPjvC9fLpvz7oSlptNl2Sxr1zwofjTg==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-color": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.22.12.tgz", + "integrity": "sha512-xImhTE5BpS8xa+mAN6j4sMRWaUgUDLoaGHhJhpC+r7SKKErYDR0WQV4yCE4gP+N0gozD0F3Ka1LUSaMXrn7ZIA==", + "dependencies": { + "@jimp/utils": "^0.22.12", + "tinycolor2": "^1.6.0" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-contain": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.22.12.tgz", + "integrity": "sha512-Eo3DmfixJw3N79lWk8q/0SDYbqmKt1xSTJ69yy8XLYQj9svoBbyRpSnHR+n9hOw5pKXytHwUW6nU4u1wegHNoQ==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-blit": ">=0.3.5", + "@jimp/plugin-resize": ">=0.3.5", + "@jimp/plugin-scale": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-cover": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.22.12.tgz", + "integrity": "sha512-z0w/1xH/v/knZkpTNx+E8a7fnasQ2wHG5ze6y5oL2dhH1UufNua8gLQXlv8/W56+4nJ1brhSd233HBJCo01BXA==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-crop": ">=0.3.5", + "@jimp/plugin-resize": ">=0.3.5", + "@jimp/plugin-scale": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-crop": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.22.12.tgz", + "integrity": "sha512-FNuUN0OVzRCozx8XSgP9MyLGMxNHHJMFt+LJuFjn1mu3k0VQxrzqbN06yIl46TVejhyAhcq5gLzqmSCHvlcBVw==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-displace": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.22.12.tgz", + "integrity": "sha512-qpRM8JRicxfK6aPPqKZA6+GzBwUIitiHaZw0QrJ64Ygd3+AsTc7BXr+37k2x7QcyCvmKXY4haUrSIsBug4S3CA==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-dither": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.22.12.tgz", + "integrity": "sha512-jYgGdSdSKl1UUEanX8A85v4+QUm+PE8vHFwlamaKk89s+PXQe7eVE3eNeSZX4inCq63EHL7cX580dMqkoC3ZLw==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-fisheye": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-0.22.12.tgz", + "integrity": "sha512-LGuUTsFg+fOp6KBKrmLkX4LfyCy8IIsROwoUvsUPKzutSqMJnsm3JGDW2eOmWIS/jJpPaeaishjlxvczjgII+Q==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-flip": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.22.12.tgz", + "integrity": "sha512-m251Rop7GN8W0Yo/rF9LWk6kNclngyjIJs/VXHToGQ6EGveOSTSQaX2Isi9f9lCDLxt+inBIb7nlaLLxnvHX8Q==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-rotate": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-gaussian": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.22.12.tgz", + "integrity": "sha512-sBfbzoOmJ6FczfG2PquiK84NtVGeScw97JsCC3rpQv1PHVWyW+uqWFF53+n3c8Y0P2HWlUjflEla2h/vWShvhg==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-invert": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.22.12.tgz", + "integrity": "sha512-N+6rwxdB+7OCR6PYijaA/iizXXodpxOGvT/smd/lxeXsZ/empHmFFFJ/FaXcYh19Tm04dGDaXcNF/dN5nm6+xQ==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-mask": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.22.12.tgz", + "integrity": "sha512-4AWZg+DomtpUA099jRV8IEZUfn1wLv6+nem4NRJC7L/82vxzLCgXKTxvNvBcNmJjT9yS1LAAmiJGdWKXG63/NA==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-normalize": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.22.12.tgz", + "integrity": "sha512-0So0rexQivnWgnhacX4cfkM2223YdExnJTTy6d06WbkfZk5alHUx8MM3yEzwoCN0ErO7oyqEWRnEkGC+As1FtA==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-print": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.22.12.tgz", + "integrity": "sha512-c7TnhHlxm87DJeSnwr/XOLjJU/whoiKYY7r21SbuJ5nuH+7a78EW1teOaj5gEr2wYEd7QtkFqGlmyGXY/YclyQ==", + "dependencies": { + "@jimp/utils": "^0.22.12", + "load-bmfont": "^1.4.1" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-blit": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-resize": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.22.12.tgz", + "integrity": "sha512-3NyTPlPbTnGKDIbaBgQ3HbE6wXbAlFfxHVERmrbqAi8R3r6fQPxpCauA8UVDnieg5eo04D0T8nnnNIX//i/sXg==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-rotate": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.22.12.tgz", + "integrity": "sha512-9YNEt7BPAFfTls2FGfKBVgwwLUuKqy+E8bDGGEsOqHtbuhbshVGxN2WMZaD4gh5IDWvR+emmmPPWGgaYNYt1gA==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-blit": ">=0.3.5", + "@jimp/plugin-crop": ">=0.3.5", + "@jimp/plugin-resize": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-scale": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.22.12.tgz", + "integrity": "sha512-dghs92qM6MhHj0HrV2qAwKPMklQtjNpoYgAB94ysYpsXslhRTiPisueSIELRwZGEr0J0VUxpUY7HgJwlSIgGZw==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-resize": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-shadow": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-shadow/-/plugin-shadow-0.22.12.tgz", + "integrity": "sha512-FX8mTJuCt7/3zXVoeD/qHlm4YH2bVqBuWQHXSuBK054e7wFRnRnbSLPUqAwSeYP3lWqpuQzJtgiiBxV3+WWwTg==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-blur": ">=0.3.5", + "@jimp/plugin-resize": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-threshold": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-0.22.12.tgz", + "integrity": "sha512-4x5GrQr1a/9L0paBC/MZZJjjgjxLYrqSmWd+e+QfAEPvmRxdRoQ5uKEuNgXnm9/weHQBTnQBQsOY2iFja+XGAw==", + "dependencies": { + "@jimp/utils": "^0.22.12" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-color": ">=0.8.0", + "@jimp/plugin-resize": ">=0.8.0" + } + }, + "node_modules/@jimp/plugins": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.22.12.tgz", + "integrity": "sha512-yBJ8vQrDkBbTgQZLty9k4+KtUQdRjsIDJSPjuI21YdVeqZxYywifHl4/XWILoTZsjTUASQcGoH0TuC0N7xm3ww==", + "dependencies": { + "@jimp/plugin-blit": "^0.22.12", + "@jimp/plugin-blur": "^0.22.12", + "@jimp/plugin-circle": "^0.22.12", + "@jimp/plugin-color": "^0.22.12", + "@jimp/plugin-contain": "^0.22.12", + "@jimp/plugin-cover": "^0.22.12", + "@jimp/plugin-crop": "^0.22.12", + "@jimp/plugin-displace": "^0.22.12", + "@jimp/plugin-dither": "^0.22.12", + "@jimp/plugin-fisheye": "^0.22.12", + "@jimp/plugin-flip": "^0.22.12", + "@jimp/plugin-gaussian": "^0.22.12", + "@jimp/plugin-invert": "^0.22.12", + "@jimp/plugin-mask": "^0.22.12", + "@jimp/plugin-normalize": "^0.22.12", + "@jimp/plugin-print": "^0.22.12", + "@jimp/plugin-resize": "^0.22.12", + "@jimp/plugin-rotate": "^0.22.12", + "@jimp/plugin-scale": "^0.22.12", + "@jimp/plugin-shadow": "^0.22.12", + "@jimp/plugin-threshold": "^0.22.12", + "timm": "^1.6.1" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/png": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.22.12.tgz", + "integrity": "sha512-Mrp6dr3UTn+aLK8ty/dSKELz+Otdz1v4aAXzV5q53UDD2rbB5joKVJ/ChY310B+eRzNxIovbUF1KVrUsYdE8Hg==", + "dependencies": { + "@jimp/utils": "^0.22.12", + "pngjs": "^6.0.0" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/tiff": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.22.12.tgz", + "integrity": "sha512-E1LtMh4RyJsoCAfAkBRVSYyZDTtLq9p9LUiiYP0vPtXyxX4BiYBUYihTLSBlCQg5nF2e4OpQg7SPrLdJ66u7jg==", + "dependencies": { + "utif2": "^4.0.1" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/types": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.22.12.tgz", + "integrity": "sha512-wwKYzRdElE1MBXFREvCto5s699izFHNVvALUv79GXNbsOVqlwlOxlWJ8DuyOGIXoLP4JW/m30YyuTtfUJgMRMA==", + "dependencies": { + "@jimp/bmp": "^0.22.12", + "@jimp/gif": "^0.22.12", + "@jimp/jpeg": "^0.22.12", + "@jimp/png": "^0.22.12", + "@jimp/tiff": "^0.22.12", + "timm": "^1.6.1" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/utils": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz", + "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==", + "dependencies": { + "regenerator-runtime": "^0.13.3" + } + }, + "node_modules/@jimp/utils/node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", @@ -2462,6 +2856,11 @@ "react-dom": "^18.0.0" } }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==" + }, "node_modules/@types/aria-query": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", @@ -3065,6 +3464,11 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/any-base": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", + "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==" + }, "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", @@ -3376,6 +3780,25 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -3394,6 +3817,11 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "dev": true }, + "node_modules/bmp-js": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz", + "integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==" + }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -3447,6 +3875,37 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-equal": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", + "integrity": "sha512-RgSV6InVQ9ODPdLWJ5UAqBqJBOg370Nz6ZQtRzpt6nUjc8v0St97uJ4PYC6NztqIScrAXafKM3mZPMygSe1ggA==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -4095,6 +4554,11 @@ "csstype": "^3.0.2" } }, + "node_modules/dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" + }, "node_modules/dot-prop": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", @@ -4768,6 +5232,11 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/exif-parser": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", + "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -4847,6 +5316,22 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/file-type": { + "version": "16.5.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", + "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", + "dependencies": { + "readable-web-to-node-stream": "^3.0.0", + "strtok3": "^6.2.4", + "token-types": "^4.1.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -5123,6 +5608,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gifwrap": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.10.1.tgz", + "integrity": "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw==", + "dependencies": { + "image-q": "^4.0.0", + "omggif": "^1.0.10" + } + }, "node_modules/git-raw-commits": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-4.0.0.tgz", @@ -5195,6 +5689,15 @@ "node": "*" } }, + "node_modules/global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "dependencies": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + }, "node_modules/global-directory": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", @@ -5436,6 +5939,25 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -5445,6 +5967,19 @@ "node": ">= 4" } }, + "node_modules/image-q": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/image-q/-/image-q-4.0.0.tgz", + "integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==", + "dependencies": { + "@types/node": "16.9.1" + } + }, + "node_modules/image-q/node_modules/@types/node": { + "version": "16.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", + "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==" + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -5502,8 +6037,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "4.1.1", @@ -5703,6 +6237,11 @@ "node": ">=8" } }, + "node_modules/is-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==" + }, "node_modules/is-generator-function": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", @@ -5960,6 +6499,15 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/isomorphic-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", + "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", + "dependencies": { + "node-fetch": "^2.6.1", + "whatwg-fetch": "^3.4.1" + } + }, "node_modules/iterator.prototype": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", @@ -5991,6 +6539,22 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jimp": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.22.12.tgz", + "integrity": "sha512-R5jZaYDnfkxKJy1dwLpj/7cvyjxiclxU3F4TrI/J4j2rS0niq6YDUMoPn5hs8GDpO+OZGo7Ky057CRtWesyhfg==", + "dependencies": { + "@jimp/custom": "^0.22.12", + "@jimp/plugins": "^0.22.12", + "@jimp/types": "^0.22.12", + "regenerator-runtime": "^0.13.3" + } + }, + "node_modules/jimp/node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, "node_modules/jiti": { "version": "1.21.6", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", @@ -6013,6 +6577,11 @@ "@sideway/pinpoint": "^2.0.0" } }, + "node_modules/jpeg-js": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", + "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -6149,6 +6718,21 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, + "node_modules/load-bmfont": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.1.tgz", + "integrity": "sha512-8UyQoYmdRDy81Brz6aLAUhfZLwr5zV0L3taTQ4hju7m6biuwiWiJXjPhBJxbUQJA8PrkvJ/7Enqmwk2sM14soA==", + "dependencies": { + "buffer-equal": "0.0.1", + "mime": "^1.3.4", + "parse-bmfont-ascii": "^1.0.3", + "parse-bmfont-binary": "^1.0.5", + "parse-bmfont-xml": "^1.1.4", + "phin": "^2.9.1", + "xhr": "^2.0.1", + "xtend": "^4.0.0" + } + }, "node_modules/local-pkg": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", @@ -6337,6 +6921,17 @@ "node": ">=8.6" } }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -6370,6 +6965,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==", + "dependencies": { + "dom-walk": "^0.1.0" + } + }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -6478,6 +7081,25 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-releases": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", @@ -6775,6 +7397,11 @@ "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", "dev": true }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -6786,6 +7413,30 @@ "node": ">=6" } }, + "node_modules/parse-bmfont-ascii": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", + "integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==" + }, + "node_modules/parse-bmfont-binary": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", + "integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==" + }, + "node_modules/parse-bmfont-xml": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.6.tgz", + "integrity": "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==", + "dependencies": { + "xml-parse-from-string": "^1.0.0", + "xml2js": "^0.5.0" + } + }, + "node_modules/parse-headers": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.5.tgz", + "integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==" + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -6883,6 +7534,24 @@ "through": "~2.3" } }, + "node_modules/peek-readable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", + "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/phin": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/phin/-/phin-2.9.3.tgz", + "integrity": "sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info." + }, "node_modules/picocolors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", @@ -6918,6 +7587,25 @@ "node": ">= 6" } }, + "node_modules/pixelmatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-4.0.2.tgz", + "integrity": "sha512-J8B6xqiO37sU/gkcMglv6h5Jbd9xNER7aHzpfRdNmV4IbQBzBpe4l9XmbG+xPF/znacgu2jfEw+wHffaq/YkXA==", + "dependencies": { + "pngjs": "^3.0.0" + }, + "bin": { + "pixelmatch": "bin/pixelmatch" + } + }, + "node_modules/pixelmatch/node_modules/pngjs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", + "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/pkg-types": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.1.1.tgz", @@ -6970,6 +7658,14 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/pngjs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", + "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", + "engines": { + "node": ">=12.13.0" + } + }, "node_modules/possible-typed-array-names": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", @@ -7207,6 +7903,14 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -7392,6 +8096,34 @@ "pify": "^2.3.0" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readable-web-to-node-stream": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz", + "integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==", + "dependencies": { + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -7615,6 +8347,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/safe-regex-test": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", @@ -7638,6 +8389,11 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==" + }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", @@ -7985,6 +8741,14 @@ "duplexer": "~0.1.1" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -8204,6 +8968,22 @@ "integrity": "sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==", "dev": true }, + "node_modules/strtok3": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", + "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "peek-readable": "^4.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/stylis": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", @@ -8407,6 +9187,11 @@ "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "dev": true }, + "node_modules/timm": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/timm/-/timm-1.7.1.tgz", + "integrity": "sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw==" + }, "node_modules/tiny-case": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", @@ -8423,6 +9208,11 @@ "integrity": "sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw==", "dev": true }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==" + }, "node_modules/tinypool": { "version": "0.8.4", "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", @@ -8461,6 +9251,22 @@ "node": ">=8.0" } }, + "node_modules/token-types": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", + "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/toposort": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", @@ -8475,6 +9281,11 @@ "node": ">=6" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "node_modules/ts-api-utils": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", @@ -8715,11 +9526,18 @@ "punycode": "^2.1.0" } }, + "node_modules/utif2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/utif2/-/utif2-4.1.0.tgz", + "integrity": "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w==", + "dependencies": { + "pako": "^1.0.11" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/vite": { "version": "5.3.1", @@ -8922,6 +9740,11 @@ "node": ">=12" } }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==" + }, "node_modules/whatwg-mimetype": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", @@ -8931,6 +9754,20 @@ "node": ">=12" } }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/whatwg-url/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -9150,6 +9987,50 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "node_modules/xhr": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", + "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", + "dependencies": { + "global": "~4.4.0", + "is-function": "^1.0.1", + "parse-headers": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "node_modules/xml-parse-from-string": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", + "integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==" + }, + "node_modules/xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index 225d647..663fe62 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "@types/omggif": "^1.0.5", "color": "^4.2.3", "formik": "^2.4.6", + "jimp": "^0.22.12", "lodash": "^4.17.21", "morsee": "^1.0.9", "notistack": "^3.0.1", diff --git a/src/components/options/ColorSelector.tsx b/src/components/options/ColorSelector.tsx index b3c2fb3..5ff89cc 100644 --- a/src/components/options/ColorSelector.tsx +++ b/src/components/options/ColorSelector.tsx @@ -1,5 +1,5 @@ import React, { ChangeEvent, useRef, useState } from 'react'; -import { Box, Stack, TextField } from '@mui/material'; +import { Box, Stack, TextField, TextFieldProps } from '@mui/material'; import PaletteIcon from '@mui/icons-material/Palette'; import IconButton from '@mui/material/IconButton'; import Typography from '@mui/material/Typography'; @@ -7,14 +7,15 @@ import { globalDescriptionFontSize } from '../../config/uiConfig'; interface ColorSelectorProps { value: string; - onChange: (val: string) => void; + onColorChange: (val: string) => void; description: string; } -const ColorSelector: React.FC = ({ +const ColorSelector: React.FC = ({ value = '#ffffff', - onChange, - description + onColorChange, + description, + ...props }) => { const [color, setColor] = useState(value); const inputRef = useRef(null); @@ -22,7 +23,7 @@ const ColorSelector: React.FC = ({ const handleColorChange = (event: ChangeEvent) => { const val = event.target.value; setColor(val); - onChange(val); + onColorChange(val); }; return ( @@ -32,6 +33,7 @@ const ColorSelector: React.FC = ({ sx={{ backgroundColor: 'white' }} value={color} onChange={handleColorChange} + {...props} /> inputRef.current?.click()}> diff --git a/src/pages/image/png/change-colors-in-png/index.tsx b/src/pages/image/png/change-colors-in-png/index.tsx index d4a36f3..77d32ab 100644 --- a/src/pages/image/png/change-colors-in-png/index.tsx +++ b/src/pages/image/png/change-colors-in-png/index.tsx @@ -125,12 +125,12 @@ export default function ChangeColorsInPng() { updateField('fromColor', val)} + onColorChange={(val) => updateField('fromColor', val)} description={'Replace this color (from color)'} /> updateField('toColor', val)} + onColorChange={(val) => updateField('toColor', val)} description={'With this color (to color)'} /> { + test.beforeEach(async ({ page }) => { + await page.goto('/png/convert-jgp-to-png'); + }); + + test('should convert jpg to png', async ({ page }) => { + // Upload image + const fileInput = page.locator('input[type="file"]'); + const imagePath = path.join(__dirname, 'test.jpg'); + await fileInput?.setInputFiles(imagePath); + + // Click on download + const downloadPromise = page.waitForEvent('download'); + await page.getByText('Save as').click(); + + // Intercept and read downloaded PNG + const download = await downloadPromise; + const downloadStream = await download.createReadStream(); + + const chunks = []; + for await (const chunk of downloadStream) { + chunks.push(chunk); + } + const fileContent = Buffer.concat(chunks); + + expect(fileContent.length).toBeGreaterThan(0); + + // Check that the first pixel is 0x808080ff + const image = await Jimp.read(fileContent); + const color = image.getPixelColor(0, 0); + expect(color).toBe(0x808080ff); + }); + + test('should apply transparency before converting jpg to png', async ({ + page + }) => { + // Upload image + const fileInput = page.locator('input[type="file"]'); + const imagePath = path.join(__dirname, 'test.jpg'); + await fileInput?.setInputFiles(imagePath); + + // Enable transparency on color 0x808080 + await page.getByLabel('Enable PNG Transparency').check(); + await page.getByTestId('color-input').fill('#808080'); + + // Click on download + const downloadPromise = page.waitForEvent('download'); + await page.getByText('Save as').click(); + + // Intercept and read downloaded PNG + const download = await downloadPromise; + const downloadStream = await download.createReadStream(); + + const chunks = []; + for await (const chunk of downloadStream) { + chunks.push(chunk); + } + const fileContent = Buffer.concat(chunks); + + expect(fileContent.length).toBeGreaterThan(0); + + // Check that the first pixel is transparent + const image = await Jimp.read(fileContent); + const color = image.getPixelColor(0, 0); + expect(color).toBe(0); + }); +}); diff --git a/src/pages/image/png/convert-jgp-to-png/index.tsx b/src/pages/image/png/convert-jgp-to-png/index.tsx index ac57cfd..1547413 100644 --- a/src/pages/image/png/convert-jgp-to-png/index.tsx +++ b/src/pages/image/png/convert-jgp-to-png/index.tsx @@ -133,20 +133,21 @@ export default function ConvertJgpToPng() { compute={compute} getGroups={({ values, updateField }) => [ { - title: 'From color and to color', + title: 'PNG Transparency Color', component: ( updateField('enableTransparency', value)} description="Make the color below transparent." /> updateField('color', val)} + onColorChange={(val) => updateField('color', val)} description={'With this color (to color)'} + inputProps={{ 'data-testid': 'color-input' }} /> F5A3!TP_b^S|4T zWNPMQ0#^79zG>cpjf0)V1@pKTf8`mT^2UGVg`e_n&JNCC9i^xIJ5}XZVBQqWGg$mr z-uS=rCJyhO>W6}Lgzaoyp4xg!Pd!F8vsY6AKM}z6Hs%BD< z==Uiy`dii*H}5O3VY8UZ_KwvxNxreWdKLu5R? zPdYEcm%fk2ptFowX+wFcZKkrvrN3@tsMKS_JygeoK7OIK2VAn^t6^#AQD3ahNNPS{ zVV(#dx;G9_T=XyHX&Ogc2;ew(N?D#$&!9A;bbKkp6qc$YwIfYt+r&d^*$z=EOENd9 z)0Glw?$3&G*+E{HptnBIOlHO|75ga57&z^#uYyXIaJ(W=JSS}O(7T1?B|&wVJT$4R z?wCVE+pbEJ7+_Bawfz24U`!C5SBb|}twb=*Y(pdlqNydWq@+HImY*Gu4Uv&yq-CU$ z@OAyMv*d1AcHaDE-I9%|KFoYdz0>s;x;D!BO~g$fy1$s16H+@dtZemb9%7s7rpiQj zKS=QWd*Bcu7qdxgy87Nt+d4}@eB8e}2D3cEl-2s0NhwI_h+D`Q-)BXpwMxFXISfkP z`Rl@0JtC7Ui675rwA)|AVQHR{8;6iqha}A`*zNG9mC*ELO4Vcvcbc~mQ=`o$Vfjl^ zQBk`E1TrO{Tpe7$y{DOq%D)#qF2Xid+1$)O5*XQuJv_^Q1okK6w0GP%Lg&_i&y4M& zb(QnYu{c~4yl!1~mFIeJ-GkvCEPaanrp6os6pp#!M!y(?KLhdN#{Kot-J<*94)N8m zJpID&b>jOdbhx>fM4-+(iay;*~jTZq9&6HxEajPO<; z0p`os2R?Qk%3UI{dt8GhY}N~8(|*W^<1!+NSX;JVc@zj!96M6nWjhaBiwHXyo-wT! zYAEij6baw&YIY|knIM|`OXOEhx^1I>2{-lgcR`AB!w-r;P}!S6(_Y5f(bee{%*qe@ zzGT6TUoaX|D`<^gxEfF*uIvX(bN$UwhV%~q^1|HXZABg{K+~0BF@;|8#_}zp5FN9A z;d+DiE@@iotB+W>u-_a%;b4;!4G~IGrT~!S(#TuKy(|_V#89gmy23DmL|DMY{S2Yv z#rI+2nb)d3v@daYzl`P-KpAG_2QevsP*yfq_L7jidLA`=<<_lp@*$H*uDr>f(@BEN zueg)|h4!Y8Yw@jBnT;Z=L=>^@coKY|i5S2Q!25m67QD2Kp~wykK1cXYo?z{0 z5yGSA_S!mC!D_OEEyuKk0U8=VJ*tK%7JUq-Mc526VUuvvvABmgLqor`&Qs{!E83*& zg-?E*F+A6ZiIH1QT}!U%wR-gjGBLd`iW>{wP(6nt@qI5<3SV6ZXLC~9pJf9nq4%_Y^+-r@fk8KIpG%a}XH--N_L*#W!L14Vwf%Ic0v7r2-We=KyTs%km@gPN>4jQe@_J70WO9-ps+bT_XYh0kt0_}(4P))s7LqW_ z4OYg3cbO;B(X^>kuSYjog$M;HrohA}0S7F7Ov2sobvu!^Zl^k=YsqyJrDp|dw^f^A z20WA z;gIGKm*#kh@F62qMOK3A)eZ*Fy=la{$C{&CYQ{laWIXk?r;9Z^<-mKqaUa;r@P`D7 zTy>Xt)T7@!PK+bLn_O@giVD^kU9Dd4B`$vKsoi|K&I>7EM78Q#Swi}pF0rZ2tnqfUZ$%qw$wxQQKtwlL9P z53s!0YZBxK^BFw-FgCkg3zV&KEneZ_tqX~!x^P=^%f4``wvuU?{zI=N_^GAXz-EyzLamZaRt@-P%wMtR_vzD&%k z*%1sBAXl91RCYygu(83l3H4~=v|L1Of#%& zt>PYdA10_YH!YZD!%mzIPOM7xg~&vfv3OSpNv`m(l!WC@u0w?%PpSr78sY*X;SRsa z_EYC1^(TxDTD4QBi1A=iRVA7V_NfFTpG69tEtFnCs&b38QX=!KqFv+wMGn5oAi1CR z(6e$x+|a0J9o4Uj%REJfAAy%xGR~BXipoPO_4-8Wi_ZHP(%~65D+51c*!@aUgh z{i)B~G39h0Opwnn`r%^_!SC8wP9N38Ly_?xgeJH+Q{FI=p$%gjD7>Ly-%RN9@w zcSUQeS*_VE^;(Qd?Aw_XP)@9pKp^Dgr2Ley;%_MUL#&B;#6oj`RtwYqyBtdGr+Ezb zt}>*8B=aI|msHhkco+y{bNgt^8Pq1SL^7l{Ey~2M)Sk6#waM)?w?wCI)Xbmh{?|2R z`Yf?2sXT;l{x;Ong?uwlGDo7Dda-j`+Z>ktMORSRBElF7z5j{5n?^NxW~Mby7dlMKb5XM455Fi~l96;NP+m@}B)783-9 zJ~MdRKGGk?#L;ls%A8bND}1aB*1lkTSaiF%yp($w!K{XXYhKk#(>Vwp5baBFTKHKg zD5W3KyoaY~T25ZM!-+!{eD)%taHTuo@<(C4+%ah~37ekTDYCdRo8oX{+9H43%F%n3 zz56+I>Co*Dwddr&ixn|`oPRFdlcSWIVqZXIt9W0DR7<}av`1ailC1FTjMe4nt=fRY za>wbuBid~8E0(S!b z&%KsaZyo_xwVux_ejQUTl6YaG_jbD?D_)z0X|wO0y;!4OPl)1?|hzm=+tw>Tl+Tg#!%Gz=Q2Y76$D4BRm|2#yy{mT#m>0_cueL3rN@$4%_hsVeD{NRr*@J$odwHh~@1jbUmzWyKEX zTzl$0Z0Zxs%x%GPr*os%A70;nbh#IHAhhHx9;9gHod`0S_U~ORs%kBud@Z>!vBhE zU|pzRHT@Pb*z=s|e{+sdlg@H-@R6JQC&tUY;LZ;ix69+n1y@Thhwuj*bJ($k771m^ zSY$T`p+gMX6qhXp=MVe7N1wf7Tj}@4Vg{`-Fy0WM>mEjOpJr9Nrt3oZ5$?;f)QY_A z$`^*;T{}HU7r(x1J1*X{pBU}l6BG|Xj7HM6gs-W?>C}&|^;Rw_O*BKnt&|(A;v-3_ z?O}z*Q zsff?#bg%Co-mscY&@hl^*da`Q)#blpF0^_MX}NH%JYuck%7(Fy-DI}?hzR*Ak-Eya zH-S2ld9yI-)!xFQHqBcSM(ei}SO{C5(&s<>L+q6RB65+JS#_bJ>7+k&?UDzYGmh9@b~>)DSGmKZ4L?Bl`2o#E z;sV~yVPUF98R_s&9r8etDNiN?zdzqcIQ5u_3LwFc&cvIf(!J+Cav1v}m}*A){`>h@ zoT$J2l14N6mQTi~=UtBgt|!hl+@9Q^N)^Esz!&Vlut(;9*#qB@Wy2)(`BFO%ZRX_q zD}twvxA@Fk)e)J6V%s7J~U&9h)M!(~qihbZW; z`36e*gmD%?k%pt1C4uHVZRd2TR1(Y$u+Og^t;uLmk)h0!o04$LAJ(GS;8-eCG(vAM>AjKgSq^hp zcHY&RqsVVY<+qv+8)K07TkmpA5{=1yU()h>6Zf0WksJZ8p_<5}IS%28HR_pGN#@E| zm9_GEA~)mV9_Tn|vk4Jsh{RTz2?8k1QO~43^7bf`P5f(LCkg3PPpS)ap54|F>FrKQ zTH9YrUmL0Gjdo)V5dw$F*%n+xH9`pz&{5AnHGLI-&#|$lfyFajlEEF7W$qD9yIveZ zsu)B0L#_mgOqDNjIZs!&q}WMdBfIZgBhFgFvg>->m+DN#R#NZs!VjBR;qE^uPph<5 zgtNOVVe zW!vyP&?iJ^ya#M5zp&eskKQ=N|4yl-LpEI8Sa2la)SmvSh~=p;cPuN~S2#0vr8nWz zLpfb%AopDK67mQLDfds595dDKgaz-+WW4yUypxKfH!Zwu%sI576x6drZVBOf9;t#>X~(X3~YRh`A9 zmC5&OG}H|3g*cs!%UMU(Bj8+Y8;Cex)cgo2+1F|NsE+VVy#FnZ{RmW+^;>LTr}X~5 zIF&mxLdc{C;cg2mFM8A}FMd#!7F%ev5s=*yOMwyh9vum+i@X|1+S?a$K?B7O0s>8J6?KL{NnW z8t+k#1_gyXN>FD@G(KN^&$0aoEHY<_eEjf2Wo~*-CR6`?8O{)r0*>vBZm(0l#_gBX zxu+_h`p0j-As(12vvp)nLfC7cuszf+5x~rnX{Ro&_1EdrmHHKIZ$*p=FGl(yz@qbv z4GP1ZyJ@mnBM^FDw=VCz*Mi8<-%ex^-OVF!6`;Mm+wbPz`#QEgp7y@Xg>%&Zc=S1P zV2=C{a@|+PE1H=6L^5pHgv5CatFsQ9*L&93SKd5P2KP&QZ>s|j^bdveV60je;JU1S z=F=!91aXc`*RHyUjJu9}QEDL4Kl4g`@C)!bjElc8*e|-{D7uWA@v(daJVy>V{tY!_ z%^dQy{s43@-ai06Wg;hGRDe5)3MN6!IokU_fWhEpnYndhEZ2!LAR`2`i}3EfgF#ms z&KqZ1yuvWS8P%HSF_<+JK@8e05Nva0p-zR_HSS_3$!UGw7p2X5rX_XMTv}Od`@cfX z$jHTMo@+pU+m^wprV5EN2F?U6jiOS0q;McNkR$UpTu(bo`0TOb*E)SJ6(0*Jzs@Jt zMZr~$a%;eay$oKqt1$ZZ-4XIun!mIo+&ve2Z$~aKPQ86v39)idkwq7}jmt3Xo9S`+ zEp-aj0Tuy+t!hy%0U_I|{*;ofed~1qF9pbK_v{?`5kP$~UGX`S zdhcTP2oOl^kaaozbCS`Q7acXMMRWSqgZ-sfCAE5%^rd0o^ot~P?L77U^|3pJsKYk| z4mo5o&})+gk2tam)MVRsRqWF}B&z5=oON_@Y5D_rp>-&?FFn^78Is}@$Od)*?b&OSi^L_mQZYCaEn`k;Lz$!kxTj+ zD_JW{arJxrf4xvY<@pVQcsz*l9j8Dx?+KjU5X+B;!*r&9VL7t zOwm?fQf;7py){VA*^f2b5YDCR?<`iy(}NgQ8}plXAm(xipi%Cq7@%J=e^8vsV#BYY zv;VgG&OV2d4Ko@TWH!N8xIL(7Q}^+!Mk9-Ld$X?Zp=gGkH;SK)i&u>jj0{yB6{QUA zt1N>9OLolFe(2scgmZz6J*-svzGwY|=Od8)p%lMW&b8pyfc+Z%!wlxXa2#>0#IEz0 zAp0O4H%_B_lyy<3N2lDDtPC$d*3By?DzRW^QY?(|mM6n;4QHHOaF|dMDgi)lZG!aMT&@O=bd_lL(erm)f#dQ^=;Gx3z}HkIJq?SW_Qbkm|D6&%^ibGhsK-M$Tk&#&70M0QZ- z<8r*`%Szs%Ie=?m5nRjQqJL9Ji&z0NUX^aqopqN zKjceq4(!Z?(W~mS(>N%sSE|#~sH87>10n*D!#V{S7vDe!sBtMO4b_6u@kP|8moy3! zyHXA!oQ>7$HF{#zu1_=hY#MJVnXpXJAdewK~&6F(br-rlL zRW=$-!#W4M^9AF}^e-x3nV@JJqW*Az_k}jqC@7uc>NAUiLPqd7qb`5(y~~>p)F6+&7Mic7!Yr zCsHhb1dbqr9Ff>JJDKc9hHp48`{qq*1zlWZN~vvLi$dUFcxofUO`(w>B&XUM&?`bR z;S;qmG#hyQ)WcgLO$Va3p9GEEe;RKF3Bd}b-k&f0YHF%auDx2MV!O`uH0sOs`Sb|z zFY>ov4cM-GWJU8E3~orAs@UprnDfOMe$ssH-r9dZ##xF5DYnz&3eL4Ep6QAO6%XMp z>NHu13J8oTEX5||QQLm#(~s|I%DU1uGvq}bv%AnCN~SnJ+hp^uA-jS3mX%16NhWTZ z+VzbFYVzi+tLAOcSt;u}>DZ*?&$rx(A*$OD9nu;EHAnI3Ovabc*xq9tH2#*Eq-nCX z#xbbMUH19PU`&uDPrJc5J{SY?m_3N-Mwp{wa%4zNg8asIspi2p@yQTi^&9GtFhR3!D&mM&mkFMoaC{H@Q(KF+1iEqpXCrr>k$tH18r) zsK^#^Mf<~8LT8hUpN4$ZwXL*bObwk9bs4iDM4Zd!rKW7a%UL6QHszQCY+38N5m+o5 zB7j&aQ=GNit3$0U>vv=BBM`1y8xS>6xjK687)ad{(0e$5#z8M6@KKRSXK zbVNNu0^T}0 zJA~T%HH=amhra$2t7#WC!OW#I4b3HbQEPcYZoo>SC93Dt|!z>7Ph(C z@jm?a&Q_lc%y@7$Avy2aKC@DMG1HLnhsA>T^?J%ng{I|g!J)J`mrr*|I zz2r-(Y~j-oRem&->q6;vMU@&dBJ?W*;R=6+)3M;1mf-Aj7(4P01o%=fZ2O{j3$gLF z;sM#~?bh9%-Ib6-{!c!p1CPK^(Zwue0pCs8j;tiTMZdKAV)vaq7q{ZiP9vnuJm2q~ zULFF1Eo!&c6*`54yB88?R@vS7D(10Rf#D`m;M#>bs+MZ=b^%;?O|G(iqE$i$`0`pf zrPCI=Z(Wy(2$ZX$2GpxUCxTdLXJ8!c@!=v8RFeZL;N`;B*HS;Nrau$efX+P@Z{D+w zvXnK0xCZ$Djn$B-fY>dVFD#8!9L=u&c}F`=X|l7C=7je>zTv z8!X3fY}w992q1ofDvj(s-pgW5N35@k2~B(dYL;QiSu4S^#8Rg0iCNzf>9~h&NjyO)@rzCGm z#C5b>C`ep~Qt~)E;5wl?N%pOUDKVGU6*ZI96P?N`+qHEd$`N~Ki|MlA6~bAYH7ret z_xfoE(+KRC3!tHP`#z~Qa2i_0n?Hx@WBco!tbGAs@FN8weSyetmktGDB`$3rM!nJv z6s_(rDat7-VKMkW-#}ubva^Ah?85|VCyKRMnF4MiBR65=&EQ*D;py7@?_h)@`b9lS zmgzw!b@-*+0ybiL*q*P!H{wP}$u0j}V%$~`Uz z+HV@)dk7Rg4pn=tCjC=?cbE{NkC72wAiQh7W!Fo zEvg?H_s;y;-(ZY3<-4=bB=a5jb+ljEH=(^E_NxX_=|i4Z%u8RLT1RElFl|D;FXQ)Y znvpN{N%(1h#R&SliKH%uRL?oOw9Dj8S*C#eh5>|uUb|yGn@D?sjLB% zda+3c{4sr5}@NZ`Z=O*V4zb;AIS}VbjXmje;Rs$Ia|Gzj| zxaC9k`$yoy=i*1+1UJYjKRhwZ;Usmh&g=3o14i1T-Q$uYy^Nu8f3dICZM2?6WB$Iw z(DW>=Zl@lo*w=QHSR6op5j|z#Dk8MPs#S2iX+yKgvhH3-WKVZXLs_sL(x6A;spxHP zWnI7)fatWCcl-6gXYaG(_z*}rKCC?gwS<)c$zT7c;E+nVOQmBX@I&7m9sQP+`@xF@ z**DefHRn;)@jm-xA(xqmtOe7nzr zQL4~O%kMm{EhupxBjdbt^+mvCYhfKIg6u7z>zPF|X&bX?3?-)B^K}0Qn|&JQGwnKWpf>5a6%;6La?UJ?YZ$; z%-}o!&Ozz)H$mEkTB_QGlA1&ABX^Aa7gqHI6&Y-S=q()Y6g}l$b^LBK+HJ@}a%*Sq zdeArw;^$Sp=-A7evyEa)yvp?y+k0C&5ytp`l1euJ?cBOr+gl%Vi@00jccaivS>yLn zZRWSxgPpE0lQ%{ho$pA0(H5>(uW{UF&jlfJj)RhRw z<9Zr^g#NNEQbO7i0jFFx37#@EMBnH2&o7o-z@t}b?yI0bu~8TD3fV)tviq?b9Cj*0aqu+LX{+i zE2B~WEz7`^J2+#_6}p?aTAnx*FDo@@O$BqdN)kduLxICO9A6&Zp zvPrE-;|Mhlx9RvREcg6U9I_y<5x!k*nPWIx*3a3}Fi3EyRAaF-rLZ%4kjKD=C1hMV zlj5B4-01RMY0d?%a&?4qhBU<|3O{~WvNn=WAHy~E`3dX4p|@GWD-wc~qF}rJ*wYko zyk)1p!2KW%QW@B{jZ>lr!ae81A8PSG$L#2x7u0IzDU1wyOd9i7uMkpl2w*S^#kmyI zekNU1RaSIMZiq%TQYKbBjXlnEBfNIB1Z7^l_4&h7aTT#eZdhjzlyALU#4*ixS)R=* z4}(zy{MV=^{5OXAb79DNt7&dsFmUI>~PN|&UpMDpP<-YZV=l`>_mdbaG&8SP?$xOiaj~aO?I+cEov8(@ z`Vp2|Q%Udf+jZ9f&)Eb|7DZC>}hZ>x^ zc67OndXoxcP?4*WK2ox5K061r;D+F;3|zf1;-uwZJvk^3D|Tl++8;bwHc+R73!q@_Pg<9(+mG$kMa_-yd!!HGU zxUM%loCVN7(Ty3^wXD-yQCr$7H-{#SV9*$PR2zrBtWw z)%yk;5-|Fn=#}k@na%A++IbTlx=8DB&3*jySXCrz1v6apZp2~CVR+vifQ?zd*IIe! zBy9Iw<(5m9rFES%ba}ge@MI0Mlm)~&o;U|i14Lms&Obm!MU`QA>x5{XKgC6Sif+YG z$kq!-$fzc1;<=*Qj%~M_{5qkQOWrF8Y9{<|+>dZFGBPNbNI_KQ{B7%*=#|aemqPb& zh|7zt?E9^A8doN)w|zFP)jrJ@7YV~BRg9pws8u~vrx3ShKX~&m_#_}t+CvKc?3mXg7bb~YdRnGl)@@tjzMXPz2*ESIB@i=7Uml4(U8?@q-Qq_ZR zzy2hh#}f*kLRYg(_)4zFMxr&HG-;!H1c2((R~N*gBo0EY|0@dx_TSz`31YS+Nyotl zd7{r^up_Tpn5&|4HpqU;RRlnO7@$GnUFMaUmataPeN2 zD<*lik%bZOkSdZlaLxS=`hzK6e;;uttd+BouCZc`1MRVeu+{4?D!ENy&3!jTI5w|Qn_wI-;~6!itk%VDT=^E2+TU+FOFuwnh(4ggGQgg!b{?B(Zp#nyJD!M4 z3E>{70-!A>h!~S+;_SQJ%`w~ks#;3&1N17U&0jJEHKW6>!D&`2=v2uzEb=qZOnkQG zBew}659AOu-Twd{zjaHuG=!r2x7r?fpp;e=e}!skN>D(u)W>PKkhRB%c+p`j|vJgHWBT| z_qEY+6n?!UWYNX=FNvH>hxcEjMtRG!LdK2TQdvDX!04oHGZp6$m7{nk5{f#kAZ?hQS8$W z?f+~UybQMwcjtOn_Yh@F{_Nu(hT3$3)?s@y-NF2F4R$kVaE#rmSIR9=uo7DNERK`< zjo9fo7UgH3w`MigwsHe?f_xk{e&TXKQL{>J}!{6yk8w8Au2L0Q^_R zDDaY$s>xvE!*)yl5k03K2K~2~O3_e;wSi#V8OxtcdV(j&b2cmiSR4@iaXD#t8aoQY zea?~p!q@{BS_KB2vU{NQcA?aUne+^2oRcOx!SS=LA{39YRS&$w3Ke{V)Im>aXvH0s z`5M}+s0sor!@@Lmm(S9! zw;!T!zR#!2xyIcZa9*Q5%#gGE)$xLGFK;s`hFxsDp+z5c&OoIXYF(9cw29=DSf zVcb03m_vxc29t+#UzAuPN~Vyt@vCuEWIQ0wRm61RCgFb!6O9_m*3prAl)q&^GGS9K z^F-EDT;kNiS2i$ZcCxC(ui|l9O3K?;m&YT2DsO`jFhW#=X=`-0{1Bs1KbkWrWx}i} z8}j~*!y33}EsgO+QdOd@&*&F8J1Y`agkD1hrZcj1nX5FKPyLa;o}&37kfH@?dWR$s zIKguJ2Kw1^E9Q<(jYuP=X6N5u{p_**;67R7TD{6G9&;0)geW|KIW7YWo!o*3rNka~ z5hc`3sQ55`CiG@GLBGuBflJo)5nvc;L%q)xf=>zIag~ZM(sc~pb;DlKelE=`8K4m6j5p}6eP_>1>=nfkZCn{xec-4=VK#O~IS zAbZ~jw~*(-I4fb({kLmS%)*|C|3wEuD6Soe^l$z3n7PKp!Hp)poqmp|r9nLp;syFcbj)ppLf=K_$cUf!Fc=DSpvhKYx>Q+fU?e@gaSimY1auSjBD z%&{o0A3zvu9du-g9%q`DLNp`{!dUdEy4hrW#{tEdnsE9a%z8UURy-@oJP)30Bh{fv zYSJ;^j%3E zsv+Ku`_wjBU|?t6&H@H@e+-#re;G3S{|>MEeEznEFzbwa%2Dqa;oA?*l{pSb*Jr(} z&{NUc9hWmUO*S?`m9)kBCVUXtMkq!_1u#F<)E$6Uu!=inkiu@$FpyetlYazA<1e!p zLHD&*?Q9u=39-nKe$8`BIjP2&*>34=G>+5QvVWgbj_Pc@J4_|PEch4tQO-&!0|yD) z62L0~8q84&eZ?2kdi=r}k-O5*((!TU<;5#^j8oc|G_jTa3rvxqjVKbLV9%mN9OoVF z*;xgSwiM_(XWnKkF-;X+2FfDqyZ87J4b-CIzO%8B{Nwo8zT{)hQqDjYSv1K+TFY>L zHLLo2TAe;mY}au~sZ8=lSVn#dFM*MUIJN|Indm)1eZrMl-)3m4&ISf~emj zX4@#g33iU60kkS_^4DjgJs3S`VhZ6pM(=+<+a4k+;A)6UyFI%c_iq_f4p9IAN17;z zA;3?w4-Zd6{I~CvNQRe^l~cZd(xZizU&q#7N-NFZ(?tAuL`TEf&G&-(uJkS65fdKY z$-&i*>g%3txuXTN5YjR0ij+zo0-=)K+zDk5Gik@pfiT3NF z5@nrzG?!r5Ai^31}j1A(s=y3OvKCY5!kGs%3*-0 z0Aw5#!XskJ*dwX>^K?gJji~zrda+TJnw53 z&S20N`aQ*5~(`jC5ASD5T_;$bG~c$@gg=m#nJceXdM z+@`o6mb|riOVaHG3dc@M2X386JCi(bh{`2AdO{VY4b6Fs2*VAr*lub_7!&P&_OR(y zcW>pB*rK|Ul{0rd(9UevHw6%ycX&|jLVuKXz|5oV(~fo^H^a7{b*ADlWl;V3kKy0H z{zBhtpZPBOfpBi=@T~4uk^SDurz9ZdUr8Gompawx99b0ggP95QdvsAft*>1(H3&NOgCndLJR8Uau1SvoZ_QoVO*owU!ue&|gUuQ1niz=3HNLAEV;6ju3KmWD z8C1%YlS3}oS)NPWXz5O=1~SDH68s6=KM=BrXs8;A`!g25$dp?7I#?|F@cCJ3anI`B zJ+)>jE`w6s)qH|~QVfHXHv7HkiO*rT+6|r0l+VZ`u+r^oSl1*A{+Xac5ZU2xJc~=6 oVrI*fgN(}<9Hs_7nB?Td@r-1eWWm3DRY3iT4mO_3zCO=D&r2qf` literal 0 HcmV?d00001 diff --git a/src/pages/image/png/create-transparent/index.tsx b/src/pages/image/png/create-transparent/index.tsx index ff4a560..d17658f 100644 --- a/src/pages/image/png/create-transparent/index.tsx +++ b/src/pages/image/png/create-transparent/index.tsx @@ -118,7 +118,7 @@ export default function ChangeColorsInPng() { updateField('fromColor', val)} + onColorChange={(val) => updateField('fromColor', val)} description={'Replace this color (from color)'} /> Date: Fri, 28 Jun 2024 14:58:29 +0200 Subject: [PATCH 6/8] add e2e tests for all image tools --- .../change-colors-in-png.e2e.spec.ts | 41 ++++++++++++++++++ .../image/png/change-colors-in-png/index.tsx | 2 + .../image/png/change-colors-in-png/test.png | Bin 0 -> 3649 bytes .../create-transparent.e2e.spec.ts | 40 +++++++++++++++++ .../image/png/create-transparent/index.tsx | 1 + .../image/png/create-transparent/test.png | Bin 0 -> 3649 bytes 6 files changed, 84 insertions(+) create mode 100644 src/pages/image/png/change-colors-in-png/change-colors-in-png.e2e.spec.ts create mode 100644 src/pages/image/png/change-colors-in-png/test.png create mode 100644 src/pages/image/png/create-transparent/create-transparent.e2e.spec.ts create mode 100644 src/pages/image/png/create-transparent/test.png diff --git a/src/pages/image/png/change-colors-in-png/change-colors-in-png.e2e.spec.ts b/src/pages/image/png/change-colors-in-png/change-colors-in-png.e2e.spec.ts new file mode 100644 index 0000000..83eda93 --- /dev/null +++ b/src/pages/image/png/change-colors-in-png/change-colors-in-png.e2e.spec.ts @@ -0,0 +1,41 @@ +import { expect, test } from '@playwright/test'; +import { Buffer } from 'buffer'; +import path from 'path'; +import Jimp from 'jimp'; + +test.describe('Change colors in png', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/png/change-colors-in-png'); + }); + + test('should change pixel color', async ({ page }) => { + // Upload image + const fileInput = page.locator('input[type="file"]'); + const imagePath = path.join(__dirname, 'test.png'); + await fileInput?.setInputFiles(imagePath); + + await page.getByTestId('from-color-input').fill('#FF0000'); + await page.getByTestId('to-color-input').fill('#0000FF'); + + // Click on download + const downloadPromise = page.waitForEvent('download'); + await page.getByText('Save as').click(); + + // Intercept and read downloaded PNG + const download = await downloadPromise; + const downloadStream = await download.createReadStream(); + + const chunks = []; + for await (const chunk of downloadStream) { + chunks.push(chunk); + } + const fileContent = Buffer.concat(chunks); + + expect(fileContent.length).toBeGreaterThan(0); + + // Check that the first pixel is transparent + const image = await Jimp.read(fileContent); + const color = image.getPixelColor(0, 0); + expect(color).toBe(0x0000ffff); + }); +}); diff --git a/src/pages/image/png/change-colors-in-png/index.tsx b/src/pages/image/png/change-colors-in-png/index.tsx index 77d32ab..e2a37d3 100644 --- a/src/pages/image/png/change-colors-in-png/index.tsx +++ b/src/pages/image/png/change-colors-in-png/index.tsx @@ -127,11 +127,13 @@ export default function ChangeColorsInPng() { value={values.fromColor} onColorChange={(val) => updateField('fromColor', val)} description={'Replace this color (from color)'} + inputProps={{ 'data-testid': 'from-color-input' }} /> updateField('toColor', val)} description={'With this color (to color)'} + inputProps={{ 'data-testid': 'to-color-input' }} /> OrsHuzA|a!UdG`4jLV48M5RSa;u4~c+nAiGOG9Z&t940fm6oP1 zqeY4oL0no5HQEtvT{4}i(nzL)p(u?|2QjS_bDlWoyuZBv!ucWl*?VQ}=d#v!eZOnx z`jR~0T83I`YHDyVPl7)fg{q&X2Dml{x7-1P`Wb(ZBWf*!U&w&dCoh5<x8)^&3h+MlHR548`! zFt9kY-%=hN$G~QpJt-#Ll5#xK6LP-ofvzXS3mA8)r!Cm0^>eTFso#}tDugQ&i&6}o z<>-m3fv+a~DtkoNXnqKPr4f_$?HFcxd0Aneru>b!{l_+4v9!n5L{f5HPLK*62ttHYZr{>#=3+w$7V% zRORxKrpej@Xd(W9rm2E%Y{~++sry!$_!QQAObS%-GX-qrmB#rFx4Mkk&m%5QDtx+> zqObY=TVakb>}{m`+5;-iPRE6=J8g$q`}pQIZr9~j<%_ruWtEQc*!K(^KOyhIG$&CU z0U33ppqzXt^l`>Gv~5j~XCl$I`zEh0w+!FGq=w$3r+bk>NDA<(v*Yo(LpsM#u9UH- z$2CqoyFCfDV9~4eOZyztl;^-qj4mT(vFA+eOjlNu*RjO-tfn{P8lz7VQcqiVG{8A( z3#QZz5n<>TiN>&Yv+LfUPG!BjF>bY2)cboURVPHAw!X>e@zxFe#7k^HXjm$^R;$g` z9qDW(^-y2hG8a#EItm@_i2Ko+FoMT~^xBc7oF7CQ&a$L(d~qR+25(@K1z7b6SKP#{ zRD)abZT<8tRfLJrX4jPg9CKDtxSG{a>*4AMR{`{u*%q@$ap5!e5SS}0h2>C#jXK+# z`V@l>CO*9rwXP>;O?6M831oDt{qWsvt6&r-IV5M!DJ}VTtUZohAClA0ZuuVDGMzO4 zb>fxl+M9!~WtZBGfD5p#t7|G>s~-1HBXVxIFIg+?`Xms_4V#DiG!U4&Be3iC!~z$0i|RxzXqj})Sr z0E4s4Or+bgzo;m5tAVXe?5lOTYCMUaMfej(+(}P@XUtVVoz1NZ#wc|!FCYl%LYnf$ zV~yXvYLZhw4Bsv27pFSlmIfHmWDdmtCaI4 z_|Gfz%vf|a{Za?#i`xvy?PK=LZxKUqZjlqSfDby0pIzVhTzoGZL(xq$@O5*Nl~rjwpwwUJAA=D4~c7S~DE?BcS@Y0lYydrBi-kP~CF_-@6& zPM*nnmjw&F$7$XUpCHKtUBFZg?0zw-dlONWj$QqiD``tL2(J^&VnhH_~9U9hE6*yLfmgwCH*;d9MA+( zsOw;%;tQm3Qd6NPuVD`9#G#l6)@+czFK4NvmMi~R!X@e}WNMd@w3D~U4{sM?M=GT; z;|Uh5gCF%Xw%$KyxHERgrAs5%x$Ci^X;131C7UCYybtbjUlqCfR72a6Px@{$PS@ki zx}|0;whj+I3~MGtXEnjCcP!APclVP$nu;{c=Koshz6HyLK&6z^)1cer=`jsANQ2##N`BE z{WI{RtY_I((=qAgVxxv_-Ii>I&O%oU1cHDY>+nWMGhAX)E{#EBzpImXTH%bDpMP2- zms;*q<;l`2;^T7G<5Z)Ndy<>@;@GidV)RmuQq)vp3#08BhH@y!G5et5VKS@^Uq$=x zHTiB*j9@mc;6N@TvH@4pu067o(gy~2T>C;U6yRL%GdQG!>KF3F-~ZvB(hvh#Ad)e0 zrQ0&5Schj4%(j!W=ze>W`1Z>u7ZYh?}3SI7!4KVdb>W%qsd*db!r?%gO)pRl@j{qL5&q;uKr!xa$IS2BE>5rTP_-i z*uLtpMU8JQ`zR_>(Gn4ARyD^>7J;+48uA1&6$5`AzWO<*Cpt@2^IViuSOR497=~G) zKo~I>5r~rWVhj-jgIX%Nx8rTh_;*Z4CKW1+Df3%5RpS7_a2PRUa!DH2u5`e)2b9#~ z`f7o6uWDU)oV9WRi5@-I^#}A-K$Cl^w^=0Q#061n)~Z#-qb$ar;kSv=J%_3z!ZFIY z41l6BSt($?7vz}(Q3m_ve%|tPXCK8FsrTk&4+;$AR#heSFj~1Gb=?O@OP`51WFrBI z14)fg_f|CqnRgJ_D7NBt@Z|=$-=p^!mm7c6$MZ$%6KL9=A!yRz+=6!7IlTbRf|lf$ z>cqsz`zJtACB6IKauB+TuUF3Y)Q17X)3KTO*Px-|yWR0LXKs4z=6vr<(ZnFge;xi`9Q?ncG2ZO! zh<>u8mFz+CRt$P!*~&#<$VlH@!)PfOM#Sjd#{f=&=0rRwpDX#H2%pR%Vu;me7Lh{j zLF`Jq&w?%U5NH0i84C*9z}-!JN;OZwTLe|)g&~iWsv2l7%z%MHanpTEHeACE9=M<^ zA*)zSRjezc)w&U`Jb~j?xNveiFoT9su0#~SlSp;<$)F4 zwSbDB3ZQN91|-nK2xzR>n<){udh9p5F5wcypufoHty>NL>m3!l(pgNCAFTo` z02;j-k`1X#_AMmqe4cK^R!q7m5$xEdxpWy*776N_h~j)h)(u=MFW{|{#z2&5Ivb8c z<4l9JZ { + test.beforeEach(async ({ page }) => { + await page.goto('/png/create-transparent'); + }); + + test('should make png color transparent', async ({ page }) => { + // Upload image + const fileInput = page.locator('input[type="file"]'); + const imagePath = path.join(__dirname, 'test.png'); + await fileInput?.setInputFiles(imagePath); + + await page.getByTestId('color-input').fill('#FF0000'); + + // Click on download + const downloadPromise = page.waitForEvent('download'); + await page.getByText('Save as').click(); + + // Intercept and read downloaded PNG + const download = await downloadPromise; + const downloadStream = await download.createReadStream(); + + const chunks = []; + for await (const chunk of downloadStream) { + chunks.push(chunk); + } + const fileContent = Buffer.concat(chunks); + + expect(fileContent.length).toBeGreaterThan(0); + + // Check that the first pixel is transparent + const image = await Jimp.read(fileContent); + const color = image.getPixelColor(0, 0); + expect(color).toBe(0); + }); +}); diff --git a/src/pages/image/png/create-transparent/index.tsx b/src/pages/image/png/create-transparent/index.tsx index d17658f..e55ec98 100644 --- a/src/pages/image/png/create-transparent/index.tsx +++ b/src/pages/image/png/create-transparent/index.tsx @@ -120,6 +120,7 @@ export default function ChangeColorsInPng() { value={values.fromColor} onColorChange={(val) => updateField('fromColor', val)} description={'Replace this color (from color)'} + inputProps={{ 'data-testid': 'color-input' }} /> OrsHuzA|a!UdG`4jLV48M5RSa;u4~c+nAiGOG9Z&t940fm6oP1 zqeY4oL0no5HQEtvT{4}i(nzL)p(u?|2QjS_bDlWoyuZBv!ucWl*?VQ}=d#v!eZOnx z`jR~0T83I`YHDyVPl7)fg{q&X2Dml{x7-1P`Wb(ZBWf*!U&w&dCoh5<x8)^&3h+MlHR548`! zFt9kY-%=hN$G~QpJt-#Ll5#xK6LP-ofvzXS3mA8)r!Cm0^>eTFso#}tDugQ&i&6}o z<>-m3fv+a~DtkoNXnqKPr4f_$?HFcxd0Aneru>b!{l_+4v9!n5L{f5HPLK*62ttHYZr{>#=3+w$7V% zRORxKrpej@Xd(W9rm2E%Y{~++sry!$_!QQAObS%-GX-qrmB#rFx4Mkk&m%5QDtx+> zqObY=TVakb>}{m`+5;-iPRE6=J8g$q`}pQIZr9~j<%_ruWtEQc*!K(^KOyhIG$&CU z0U33ppqzXt^l`>Gv~5j~XCl$I`zEh0w+!FGq=w$3r+bk>NDA<(v*Yo(LpsM#u9UH- z$2CqoyFCfDV9~4eOZyztl;^-qj4mT(vFA+eOjlNu*RjO-tfn{P8lz7VQcqiVG{8A( z3#QZz5n<>TiN>&Yv+LfUPG!BjF>bY2)cboURVPHAw!X>e@zxFe#7k^HXjm$^R;$g` z9qDW(^-y2hG8a#EItm@_i2Ko+FoMT~^xBc7oF7CQ&a$L(d~qR+25(@K1z7b6SKP#{ zRD)abZT<8tRfLJrX4jPg9CKDtxSG{a>*4AMR{`{u*%q@$ap5!e5SS}0h2>C#jXK+# z`V@l>CO*9rwXP>;O?6M831oDt{qWsvt6&r-IV5M!DJ}VTtUZohAClA0ZuuVDGMzO4 zb>fxl+M9!~WtZBGfD5p#t7|G>s~-1HBXVxIFIg+?`Xms_4V#DiG!U4&Be3iC!~z$0i|RxzXqj})Sr z0E4s4Or+bgzo;m5tAVXe?5lOTYCMUaMfej(+(}P@XUtVVoz1NZ#wc|!FCYl%LYnf$ zV~yXvYLZhw4Bsv27pFSlmIfHmWDdmtCaI4 z_|Gfz%vf|a{Za?#i`xvy?PK=LZxKUqZjlqSfDby0pIzVhTzoGZL(xq$@O5*Nl~rjwpwwUJAA=D4~c7S~DE?BcS@Y0lYydrBi-kP~CF_-@6& zPM*nnmjw&F$7$XUpCHKtUBFZg?0zw-dlONWj$QqiD``tL2(J^&VnhH_~9U9hE6*yLfmgwCH*;d9MA+( zsOw;%;tQm3Qd6NPuVD`9#G#l6)@+czFK4NvmMi~R!X@e}WNMd@w3D~U4{sM?M=GT; z;|Uh5gCF%Xw%$KyxHERgrAs5%x$Ci^X;131C7UCYybtbjUlqCfR72a6Px@{$PS@ki zx}|0;whj+I3~MGtXEnjCcP!APclVP$nu;{c=Koshz6HyLK&6z^)1cer=`jsANQ2##N`BE z{WI{RtY_I((=qAgVxxv_-Ii>I&O%oU1cHDY>+nWMGhAX)E{#EBzpImXTH%bDpMP2- zms;*q<;l`2;^T7G<5Z)Ndy<>@;@GidV)RmuQq)vp3#08BhH@y!G5et5VKS@^Uq$=x zHTiB*j9@mc;6N@TvH@4pu067o(gy~2T>C;U6yRL%GdQG!>KF3F-~ZvB(hvh#Ad)e0 zrQ0&5Schj4%(j!W=ze>W`1Z>u7ZYh?}3SI7!4KVdb>W%qsd*db!r?%gO)pRl@j{qL5&q;uKr!xa$IS2BE>5rTP_-i z*uLtpMU8JQ`zR_>(Gn4ARyD^>7J;+48uA1&6$5`AzWO<*Cpt@2^IViuSOR497=~G) zKo~I>5r~rWVhj-jgIX%Nx8rTh_;*Z4CKW1+Df3%5RpS7_a2PRUa!DH2u5`e)2b9#~ z`f7o6uWDU)oV9WRi5@-I^#}A-K$Cl^w^=0Q#061n)~Z#-qb$ar;kSv=J%_3z!ZFIY z41l6BSt($?7vz}(Q3m_ve%|tPXCK8FsrTk&4+;$AR#heSFj~1Gb=?O@OP`51WFrBI z14)fg_f|CqnRgJ_D7NBt@Z|=$-=p^!mm7c6$MZ$%6KL9=A!yRz+=6!7IlTbRf|lf$ z>cqsz`zJtACB6IKauB+TuUF3Y)Q17X)3KTO*Px-|yWR0LXKs4z=6vr<(ZnFge;xi`9Q?ncG2ZO! zh<>u8mFz+CRt$P!*~&#<$VlH@!)PfOM#Sjd#{f=&=0rRwpDX#H2%pR%Vu;me7Lh{j zLF`Jq&w?%U5NH0i84C*9z}-!JN;OZwTLe|)g&~iWsv2l7%z%MHanpTEHeACE9=M<^ zA*)zSRjezc)w&U`Jb~j?xNveiFoT9su0#~SlSp;<$)F4 zwSbDB3ZQN91|-nK2xzR>n<){udh9p5F5wcypufoHty>NL>m3!l(pgNCAFTo` z02;j-k`1X#_AMmqe4cK^R!q7m5$xEdxpWy*776N_h~j)h)(u=MFW{|{#z2&5Ivb8c z<4l9JZ Date: Fri, 28 Jun 2024 14:59:23 +0200 Subject: [PATCH 7/8] refactor: extract areColorsSimilar() function --- .../image/png/change-colors-in-png/index.tsx | 18 ++------------- .../image/png/convert-jgp-to-png/index.tsx | 18 ++------------- .../image/png/create-transparent/index.tsx | 18 ++------------- src/utils/color.ts | 22 +++++++++++++++++++ 4 files changed, 28 insertions(+), 48 deletions(-) create mode 100644 src/utils/color.ts diff --git a/src/pages/image/png/change-colors-in-png/index.tsx b/src/pages/image/png/change-colors-in-png/index.tsx index e2a37d3..83722c3 100644 --- a/src/pages/image/png/change-colors-in-png/index.tsx +++ b/src/pages/image/png/change-colors-in-png/index.tsx @@ -8,6 +8,7 @@ import ColorSelector from '../../../../components/options/ColorSelector'; import Color from 'color'; import TextFieldWithDesc from 'components/options/TextFieldWithDesc'; import ToolInputAndResult from '../../../../components/ToolInputAndResult'; +import { areColorsSimilar } from 'utils/color'; const initialValues = { fromColor: 'white', @@ -55,28 +56,13 @@ export default function ChangeColorsInPng() { const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data: Uint8ClampedArray = imageData.data; - const colorDistance = ( - c1: [number, number, number], - c2: [number, number, number] - ) => { - return Math.sqrt( - Math.pow(c1[0] - c2[0], 2) + - Math.pow(c1[1] - c2[1], 2) + - Math.pow(c1[2] - c2[2], 2) - ); - }; - const maxColorDistance = Math.sqrt( - Math.pow(255, 2) + Math.pow(255, 2) + Math.pow(255, 2) - ); - const similarityThreshold = (similarity / 100) * maxColorDistance; - for (let i = 0; i < data.length; i += 4) { const currentColor: [number, number, number] = [ data[i], data[i + 1], data[i + 2] ]; - if (colorDistance(currentColor, fromColor) <= similarityThreshold) { + if (areColorsSimilar(currentColor, fromColor, similarity)) { data[i] = toColor[0]; // Red data[i + 1] = toColor[1]; // Green data[i + 2] = toColor[2]; // Blue diff --git a/src/pages/image/png/convert-jgp-to-png/index.tsx b/src/pages/image/png/convert-jgp-to-png/index.tsx index 1547413..53eee2e 100644 --- a/src/pages/image/png/convert-jgp-to-png/index.tsx +++ b/src/pages/image/png/convert-jgp-to-png/index.tsx @@ -9,6 +9,7 @@ import ToolFileResult from 'components/result/ToolFileResult'; import Color from 'color'; import React, { useState } from 'react'; import * as Yup from 'yup'; +import { areColorsSimilar } from 'utils/color'; const initialValues = { enableTransparency: false, @@ -53,28 +54,13 @@ export default function ConvertJgpToPng() { const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data: Uint8ClampedArray = imageData.data; - const colorDistance = ( - c1: [number, number, number], - c2: [number, number, number] - ) => { - return Math.sqrt( - Math.pow(c1[0] - c2[0], 2) + - Math.pow(c1[1] - c2[1], 2) + - Math.pow(c1[2] - c2[2], 2) - ); - }; - const maxColorDistance = Math.sqrt( - Math.pow(255, 2) + Math.pow(255, 2) + Math.pow(255, 2) - ); - const similarityThreshold = (similarity / 100) * maxColorDistance; - for (let i = 0; i < data.length; i += 4) { const currentColor: [number, number, number] = [ data[i], data[i + 1], data[i + 2] ]; - if (colorDistance(currentColor, color) <= similarityThreshold) { + if (areColorsSimilar(currentColor, color, similarity)) { data[i + 3] = 0; } } diff --git a/src/pages/image/png/create-transparent/index.tsx b/src/pages/image/png/create-transparent/index.tsx index e55ec98..3892e48 100644 --- a/src/pages/image/png/create-transparent/index.tsx +++ b/src/pages/image/png/create-transparent/index.tsx @@ -8,6 +8,7 @@ import ColorSelector from '../../../../components/options/ColorSelector'; import Color from 'color'; import TextFieldWithDesc from 'components/options/TextFieldWithDesc'; import ToolInputAndResult from '../../../../components/ToolInputAndResult'; +import { areColorsSimilar } from 'utils/color'; const initialValues = { fromColor: 'white', @@ -51,28 +52,13 @@ export default function ChangeColorsInPng() { const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); const data: Uint8ClampedArray = imageData.data; - const colorDistance = ( - c1: [number, number, number], - c2: [number, number, number] - ) => { - return Math.sqrt( - Math.pow(c1[0] - c2[0], 2) + - Math.pow(c1[1] - c2[1], 2) + - Math.pow(c1[2] - c2[2], 2) - ); - }; - const maxColorDistance = Math.sqrt( - Math.pow(255, 2) + Math.pow(255, 2) + Math.pow(255, 2) - ); - const similarityThreshold = (similarity / 100) * maxColorDistance; - for (let i = 0; i < data.length; i += 4) { const currentColor: [number, number, number] = [ data[i], data[i + 1], data[i + 2] ]; - if (colorDistance(currentColor, fromColor) <= similarityThreshold) { + if (areColorsSimilar(currentColor, fromColor, similarity)) { data[i + 3] = 0; // Set alpha to 0 (transparent) } } diff --git a/src/utils/color.ts b/src/utils/color.ts new file mode 100644 index 0000000..8b45952 --- /dev/null +++ b/src/utils/color.ts @@ -0,0 +1,22 @@ +export function areColorsSimilar( + color1: [number, number, number], + color2: [number, number, number], + similarity: number +): boolean { + const colorDistance = ( + c1: [number, number, number], + c2: [number, number, number] + ) => { + return Math.sqrt( + Math.pow(c1[0] - c2[0], 2) + + Math.pow(c1[1] - c2[1], 2) + + Math.pow(c1[2] - c2[2], 2) + ); + }; + const maxColorDistance = Math.sqrt( + Math.pow(255, 2) + Math.pow(255, 2) + Math.pow(255, 2) + ); + const similarityThreshold = (similarity / 100) * maxColorDistance; + + return colorDistance(color1, color2) <= similarityThreshold; +} From 5882a21441dcc579da2db49327f29b89923a9cbf Mon Sep 17 00:00:00 2001 From: "Ibrahima G. Coulibaly" Date: Fri, 28 Jun 2024 15:40:05 +0100 Subject: [PATCH 8/8] chore: convertHexToRGBA --- .idea/workspace.xml | 69 +++++++++---------- .../change-colors-in-png.e2e.spec.ts | 6 +- src/utils/color.ts | 12 ++++ 3 files changed, 48 insertions(+), 39 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 4604c7d..d0d65b5 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -4,17 +4,12 @@