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 | 158 ++++++++++++++---- 1 file changed, 129 insertions(+), 29 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 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); + const processImage = async ( + file: File, + transparencyTransform?: { + color: [number, number, number]; + similarity: number; } - }, 'image/png'); + ) => { + 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 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) { + data[i + 3] = 0; + } + } + + ctx.putImageData(imageData, 0, 0); + } + + canvas.toBlob((blob) => { + if (blob) { + const newFile = new File([blob], file.name, { + type: 'image/png' + }); + setResult(newFile); + } + }, 'image/png'); + }; + + if (optionsValues.enableTransparency) { + let rgb: [number, number, number]; + try { + //@ts-ignore + rgb = Color(optionsValues.color).rgb().array(); + } catch (err) { + return; + } + + 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' }} /> 3)=m5zjCKnfgXdIZ4f#KUVkm(EzmvwQd!6(d_l3JY1z`*cfiVEYE(!^9W?*1UVPIg;NJnBPA+eJS zia_dn7#J9Aa&t;Sc7fc($iTqBlbRPA%)r3F!N9;E!%)l+#Nf{01oA&f0Ruz+A_fMF zuM7;#6A)raGZ`4Rw=ytD+(L+{EMQ>ZZ((5Ab{-*y2$O=uqC~KHEDQ_`OlfHh44;-W zFz`k)FbH2@VBm@d%P}x8;BcD)*lq3%`3(6C=?pmxsSFAXo(y>m$qaf73JgXJ1`LJ_ zX8&(9I5RLafdGhPW@ct)VPgRS4mLJcHck#ME=~?kPA(pP9xiS^Zca{KL0&$70Rce) zE*>FaApv220RaJ!A&elM%q$!%EF1#doZJE=gZ~E@1UVQ^Fq~j!RAOKfWMmd({C|W& zhJk^JnUN8c5~1E@=U`ytWMyFn$p#28Ffej5vGQ_rvobJ)B8HKHiJ6s+T~J7vMTA3C zT+vWWLP^=k+%c?W$9UHNMq^X+#L0^mUo&E+EfWQQn*$o~wjYX8ofD}6IzH8i|%@cJ=6&-b1k)Jf7O}RTf0?HXC0O7p6>N}@2$&%zJ5y= zN*RCSR5M6Qe9l%NYsm9KNa*Z^V*RUbm0G^rRgTR3bmUv#y&V^(@abI4jAE<`eKuEr z+I-i88k=JEMBasoGJUj*e9iD(S1eR8JXZPJofBu!4Y z5H305p7B-rI`>}g!(HnIY}_}?`anX{`G{B@RbgkvQ)Tg#nydol<0s_J=@UB=gUC7JRQ&Uw_#igO_z{`6T3G2V^ z4SbTJr?s;;?P}POvfoVGCY1zE_;r%6#lfMN^NBWs1Ot2`Rc+KeYln0^S>2CyCWR^xVJM_r%^_M&FYD+x@YU z$v<2USY3}gWH(JP;*x6GFAS=VppzBHY;R^|9Y_pJ+Uj-(xwQs%ueOR&XFNlCf9 zwM}aV_m8(f^Z%+`owVYw{m0cpDZ$U4t$1&7{^itnUsn8Qcync1)XQ>%o|}&u+BFZ^ zZ{Kul?^I!<%ckW=b2ojBWj%JLuTtlvzj;cMfrUchvcALznw{+o)18t|?VNnf{!L%O z^exU+O?`i(r=L`aHa5Oz%lPH+>gGT0OAG!p9Q*CNUHshB+ow4V(~Z)eE`QF^`TjCv z5pSaCqyG%r-(BC<=N=9^YCH9{(V4Y+S+`}cHF0rV^0b{P_&lfI#7|;Q;o&(I-bdc; zUoCN1Ltg9As&M}|!K-clzKS?Db5=4(T8qnyO;^fa@OAa2G_{m+PAV7cn7|(V`Z90S zeZiM8(I+eyujt)zFWp#d<%KEREVKAlJ!)O+6x76`@^evwr_3L*@88lY^EWQnWl%Y~ zApI)e+Q5u_32Qa&xK&SfMZJ=lH`lpQ@Hfk%!e(J1ImxpUZi;glyk@VP_`C4+Vx4-9 zp3JRkt9r#}vM?l9d|}^cx8t1Db=MFRRY#FmT^E+FWK3AFqC+dFE+{B1sM^Ktht;HW zKgy3qf2vz3>9b)^o?)?zY}1-`;@qmgP8!|K&s?AFuj?{ND(BKHwzgyk24e=crbP=b z6r1chHLucBVM%Z2SN0upGqMWPyG>%tU9;x;WnNjUw=`v~1{0Im{7G9Sr}AAC-e;4_ zG2@xUv%)pUr51$8t=nE@UF57fYsu~ACc`NvKRISjd|rI?UfKL@u{+;-PB~$>X7|cK zB`ZehhFbS^Y+JUoX8+nY>1w*lXU*Kx3v|7?KCSdHV$PBcN-o|~`$p#}=Zq7dcRqS= z?SES%X5Jekd&Bw*_R%cz-hC^7C+q*rt2-^LTPxps*ZfaW{JqJgQu}el&Me73ceFDa9u$kI8PdRge*| zxZG?}AQ&eg@oLBRmlJc!KS#?vp1u9@x-Tn3e{X))8*id`SmxUHrLAQ_GhAm2uh0Qm z;%BnfZ&tEUp2lp6le~({?2;C)GpMOPC1r7Q;=;JB+WVlX9`d%GAp zzvMUhn=;`^<>SKgxeMM-n>bzhXH{vIzT)Xx(M$C#-~0Z}P*@gLI-TeJhnK~g=R2Pn zv3~RS%i=$}zq;1t+kw}iznXt8?_Pgheo5F;o8M{Af){c=Tv8%x_~@bg3YqxNQvVt1 zSAU(WsWSOpUr+ky4DH~QV_UkzCf&^6pq;hA+t2y6vYCNf24A8Dy9Xze{O`4SZ*R1{ zS~So5%H1n1ic$fW>SwGdnz`p+xc$kh;Ax%#EX#z1RMgM7-)px1d-HI5K;~c7xJz-D zmfiT9`NibXq}xmLR$iT=`ANd-#0))A6*DFV6_%DauBAJ|Q#E&o8Co?Ry>V=N$z!i8 zFBjUXJ?h-pGvSg{@-sDtNii~aH)k!VS@Y>`-qM9Bj@2tBed>6bwrQD(u~b3Fa{*qK zw}RFBTW9#5S)ozY#Q*GA*~*|Sk#uKKF|ku8CQM!Gq2ejb#c-raeM$4n7W?S+%Vgz$ zFD*&m&~w6T-nmy>W%~_f{@(A=ySiFN^Y>llr>&9Y&XUQq4EWD%*gem%*yV2%+n%Y1 zw$|NR6ZWdxw_2&nX0MLJ?uG}LBo8<{a$hJe4gaj=SD|&=$1C@qdxh+Bem|WH zCahXrmv4pUil1J|wei!-O-Cm`)zf1=Xnj0Fc;C10Fa8=`?zGEJ%JHrIRr+XcRD4;= zhNaJYDir4>Y~R1x_;hWrWx&mAmW#9XrRImnt=klKd*V!;i_^;r+>HAA6(u$lmz`bJ z_-XU$(p}S9C$hd<8w*TE}Y3cq&&yLL{Mq-%oNL$!JS-RCR%^Fz3zwC=4GoR z=S*C&d2*=_gTH~os?YaL9j)I;gig*2k&Ia#{lMK6C z@`m4|Z^5sJrw^PJcDiO~e>+kVT*{R(=>8OU7=Tz>} ztK3nXS<1Urts;Awn=O}h@Fg-{3TrQHkzN?^SSIIsz43>=?XR)K~4bZ^#V=CAnw1Dr?LhJk`x+Y0*q* zRhaUwOv&XC@0}!Zr~T2%v%cuu- zn?2{#sR@tGgIkhyN(!bgkGlQsk-q1Rf0{8fzcmKDx++%2yJzd8t*_Qq-<>q`s*wVDJAAmeHQ(Z|IJ%y5<*c;TQKfT3mas8< zY3@CAVc!?VXIw6`y|&Gpl$^URPxt7fJ>{R5t&QG(A+<&8?}MLS*S^f$GiyV)*{$C_ zg{5-Omb)!=Ii`H~$P1^67qSAMr%T>RWP1@}aa`K;iuT3jcf=Min^y5Iu*ama;{NU5 zC1+PJi`%SoI=m(_T5pR$Os>ZBNtZf4Z*yPF?8G*!;JF;r;}e%8S8SMb)^$@{$X%n! z8x?Xmm=mmP?+Cq`r*!2@YUC}oo^z@u-_|~DVY}xEPVc#F_Vt$X+0RwyYodP7{FPu|bLT$$sUO_B&wclE z)?T{eZ>GED=w|B!zFxvpysaPJm&`x;t0gtt{fgYRn$4%Tv79+yV9UOa!J_}*e}?K` z!KL=n=k^}ByJxpj&{m(%qMwtL_s4E5MsK+D8;F0r{ptQMkF~98A9c-@z8zsTma0Dz z;#NOXddZQR8}Dr&Ul!^;#-EaLHB+CpWZUPOb1C&xwT;tt{zU(Z-BT*OF=V=vub24V ztnRnnHs5aBE=#j}@#SbZ&yLyeiy!ST`uy`-&2HUI{~7)>aL?TT&ZT_rI{)^X)cxwO z1gDzJY=_am{%=|+FwJgdoOy?D`htKs?lo)u4Jot)A*O*P`a_S(NqKYZtOf{V*{^($46 zR=3L}eBZy+(&t0W>pOa}XEv#7__*uFo26BMxh=H{sb9lVs_eYygKhlls>`PWzp`(g zuk-0`qp|TFe#h6HN9y>0-@i0_<&S%%@7Ufxy(u(xRr-wevjt^;z3n-x5vA~b-^#E5 z-ZZ>#ub#SJ{q@BuXR`SD110%l-c28I*$6i}HwQ@}6jLBfzx?T84+~jRFL95rzOyw5Yz{vtx~Z2{G~^cKvR~J5-D_LLvSH52mKz5goGm!`CNTNkB)nxaS^iQRwdqZw--1F&F&*SeVOor2>D!i{vJ9r}KaOFX<))=d| zD&J<_7X9^Z)!gv)GUr}y=Q_PQ#blv|d5c*iYuJl){cj7srZ=Fil zJ^t?adU2Y4i|@Vgy>id%7c^TP{m&p$CH#~1wa=O0&Eh{8%sN|czm{$Jew=OB;%qJV z_U}>4d9Q!^+0R}TUE_2;KjggU?KL@pMcY#JHE(Lp4!d$DX;;KOeX|)|Qd8b?3eMc% zuGz@UV`9vZ(eb=vqN?9{%`2uGefwG~vvS*IO*Xc@aFcRr(<$Aqbmr;ATfxUA<}EKf zTfRC}@#OA%=ORLfCw4fiGw)w`|{<_px$tTQvW5xtt4ynwAGc36GPLlVi zT>e^dc5=%$*ICxlTdssz9QpElyJYOEt8Q6&-#vaNhQ(euCU{nY;oa;d=|+-Ut!KC} zO|oj<)9+Mk@bq!Gpvkqh3ydc%POI!weX^!o#(#?90iU&;vLR+O?=O#uS-Ym#;^~r; zi*^e?o^ikI$+WI53;p-Y)NEK)_e`*_;(+4k%~8QNOO9>yUHRjv*T&vwXBnJ>W`r20 z^#vbdpONuQ;3DH$0fp7^t>;#~n}7NB+H1?NT|R5Fzeu2E|C^m_zKK}07N$pMN$utQ zwkKUD&BXVzN8<7mv6g$5hK2^bc8!@m`O^Z2&Rcc=8FD{*I;?-eX2SGI@{8$Pz2FBz z2ZP=R7Ek{$XOkLN!riC03#ARCuHEq$tn$10(cAvys_Pej#9ppHu6S*GypNUrcd!2p z)`lQ-XA0(6o-=VN?&zEJ(Zsjg?ONlzQ5dUMpVc=;$kSQC{M*+v;wu!HfS4ceNMWHrCk%-@JCyb75TVdf~I2e!?Z! zIbM7cRW&#Fs=YL)!}sDIwYy8o8}AvMzqW<9-fQ{7w;|iTSrgR0A71o3#C?jz4zt#L zSrxzEKj&6o%DWe4e03AZedc*jo!aJlK7XGkEpZ}m#*4FuTw1kt7UqU|L_)l)wytUA zi{}$A+T5Mk#LmKJa`B7}>(}xKy)1T#nxFgczpRb)@>KtQd9mc5TU^gq@;d%`bIkm8 z|HT)V=GCq*HN4RB@q!gs+fv`NT-*CKf2d4ZF;iBEWyZ`~{Fz@4XS=@668ceNGAaJ= zz1R6$+TO;!vsuoZx$idX(abw5RQzXj7@ohj?NLSdrLyx~0lOsQzEscs*VJ11Zrb#3 z@o!fDF%aTmtItDmwaWtVtc z`S+FiO`nU^^6o$S(y{%yKO<39?P`(EzjK6pD_`LxZ;UH{U}lBdj0x-r>!OU1q5 zTjkfnZYDKaJ8C|=*byt-qttTnfx#Z1*GcD0r}z4=$q$dz&ei0ZmT`KwiL#`-`-BF5 zSS#f`d%T3D`d+=Nsj|)frk>_2R9$j&R&>5K+;{uYZP!A%Rrl8CJF(sVutxKm(28(_ zg)KS?lWVujMeJx{NI4+wEC6rw3BOmCZE5O%266Y7vPX+y zo}B#s+u^Oct8d0_Idf{=kG0!kGt}4h%6y8PC{cFgbHjtiV{;@AH*~Xdb>D5Pey043 z|8?T(FFRi5Z!?ykcK^+W&9DK8DJMQ3d-UGg@wQsRLjN+!_cQum*stxxrgGwJ9lhs0 zF&FI<*161@Ug!P&dU@}sPmgqOKQ5iQ`&q}!Mql4Q@;~FA)rlXAt+zibGh^GicrEo= ztCziO6x_4@ROz9ub!(lrheT!H6h7;#(k^YX98`5N@;p#n(82d`VepE=KC^8B>)qyE zE4=J&Q?6ieiA56D`d{Z_Bo`{TV5#BWt^nab$rXJ!%6Ix^cigg_rnO{2+N{*c&h$|FWZ&m~d|J;=&W^e*cteLXE^sWoIU>Rn{L`@D_oKST6Y zIk%>yfGoeOMyDztFmfqpeqrBfx8vBI$crI=Iis@X`bAx7HJLhT)1;KOnLjl>Iy~MQ zTR+gBeC+U>zJl&8;ck@+gEw_mTQ%0_?7IDC^Ho=$$4_TGw7EO6L9NHvg5BW5)C*1T z`46+UuX@#^<@3yUuilMwDJO%b2Nd?RY;cY|fQTZ=Wrbe&{ew zy*EjkVLEryjY5}(2`Rjf9F?B(_WzEbz4Xo0)TcJf)J`2)6TCduD{)JbdCFmqWd;_! ziZ6rX`Mzt{`CnhGC$?2BZ_%ToyrqhI0+SieXeSH#{eHXgU|4)<{tSdzU1wEk!j(_F6iEkJ|4XxM|QpFimBz*ODD}-`SOZG zujX%)1%;1=FUeV+lW=34!N7HBuW#APY2G>$I0CHCFO+TkdZ3souKu(>O)1Z-9G z)nrgmnW^oxFKCjIQ1zK(H;hD0KfEv6{^W0!(W9MhM;B)s9$O)1zIyGwzDqqP(^kSN zI9<)iRPL-zPG`1UlYX@ArfG?&zq?Ia=d(9G0?@%`1-7ER6s@zVcNHdVDS5e*{aw}L z*pRJq;bAX$-lnTeRo!m%vxA#K`_y!S6sCn&)y%Ek?;Y}sTIIAhJ5Rl1uX@gpC9ATR z)ZM9iaa#V6+BJLIjRs5Z-(2=2?pVVv39BCmPujlZzBO%W&8_uoeT{wd_UWvh6z=bF zr1(VQGU<&53>O-g&5~?x*w^zq>EfpHUjH?D+gEDmYMh@HaeB8&B4@k%l+{^3&5eJW zKE0(NEL)Z1_;UO#6UAoJ&$5F-a@+>M=n!9OJ2mj-LjCs%frj5t-b&tPSz7A3PC47v-iwvlWZQ(k6ednD z>CaK&Ob@L+`z~)|R(UFFu6*u!_nx=*In9=d6?c-Jby%}l&;9%L_JcipcFx}R6|B(izl1MpLy!?IXEZQAkA!ALUV*`#r{)&FB+~B^jeb} zYt*?cbItXmQ#XB$wRx^Q&)X}Q_r?r`42R3UJO>ZNsb6{>;a#S-S7Z64kNTx*9By+; z&wxt`5j-@={}LUc*X5bzVXcNkQZLjGF#b0 z3!Sbl)O2JLs<~*O(vq=IW}f%9q$xZ>NAp$$UD&!{v9Ib=%}Zyd9CkN6&cT-`aKYh{ z;j$~UR++@52r%|Oz94tx*JF$9SkZtBQ(1ynX**3@!rIidNMWVtPfyR8o^J)E9|^;g zfE2vtHtE!lTdccRSG+FcZQlB5tI)dlC7XgxZLK;4m)fUI;j>Aqaz1W(nB#yk17i_J zy4e5FZ0qtDUYEB@b%`y}T&lprGFdev{8W&OlYixm+wwCY;m6i-3eWt^-u0@t4ZYYj zleULwW<*^GTsG;`l$U8QUME{bdOaZ4qdCuI+g>A~mCzZ2mpOsQ;;`?bh*=@@02`v}Sb9B5{J}=$9=Pi3qpWjWtO#y~p zPo3_ab-LEH)unh=$!1lX2j?=t1D;M-UtLJnnVJ&kd!4gsYA*Y>zmNIX2^X9^wKnrw zX{hCe1DUSoORu$lU%+p$$#-eAzo+50yXj(&KE_%9yd1Xn_6ws!qIDl?mV{ktTkx+e zS^Deo7hf{{?XK^d0UpI;mF2&7Z}C0x$hYf6SFu-Zjb8iKM5jX|#8dIOw^2!2Te5>o zaR*z|hf^sR=bfnb&D?FsrSSgaWJSBcZ5PaXf1lGWWm|OSWo-02X{`@g{nfr}H|Ma; z;|M;mYeNiU%+X2f5;WOQ1u^vbLAujZee?FsD5-xp^*pQz(6!Sh$L_>)j@_>QZ)IIy z?0^M?g zX<@l9^mo?A=v5N0zPWtKTyjh-IBlw6TVL{|hPDEM3!!tf^KW<1^(yOVDXH{4bCQ3F&E>C??!Au9E_*tAYx0@OE*+n76&f(F zRqk5L=S#98oj&GC8HWrd7&w|4*G)X~@%3We`5ZepPwAOgd*EH7kVD^++&?Z=kKX$g z3J1N^S+yx7=e+J#jWylU;WHXIr!I6~#tYrs+qg8*C*uP*KZ#yr~e5&smpJZ z_Moj^TjrUm+Qpkvyg_m6wwleJGufx-v3YQhqDe`CNHI^b+sVhh0ov=fuih)WQ}VNS zQ0~FQ96nMtOB`bKL|3t9rS7_S+37@6RHur?%QOpKo?{K5SsT{y$TiRIoZUI~)Xw6p z1_p*RjS3R#D{ViN9`=(8Fp8?zj9vGXJMvi9oS-?uy;H>Q1bR)Gv}6gxlqK(GC>JX{ zzTvsjSTeEPCh1w{Zx)+t+y3t8zEo!46*kLT>tFQTbB;c@g*fK-zHW$LB(>XX?Qip& zGpF6#JhxmoGsa&_?NU<0k+01!+t$NJj~76O+*b5$eQD4a*%D@LK4b5Zvr4nJemh#~ zo3S+1@a4p}9X_^_7u}{D-loo@%-GP-P$K2N3*e^GeIy4~LT_ z`7X*z{@}VE^q*nY-GBanof%|=e*Hf2pTYW;Kqg@A1yWS^Hjp@%{^*^8~VcYQ9f@oxPXqd+fT(g}PG(cO?j3 ze4-}NxL)owDr30G@bdHK@Vbt_mnnTzb`l_IpJ#Ds(*pI?&jIw zoR_)!Kf?rY*I#e1x^7*fSXpe)Nn6>e=M0*X*??TAhBb4Z ztL~#cx4pdPclv1g9;i$-W;r3CT<_fAt3B)blxv^9a5eLrdueTbuuaz?HG6v3T-{bh zvz4jddm>a?)mSvDp#6IdA2(R$aFYAhx9Cg!*W|AS557wu+`lRHKf|-DzjMO5gvF4? zXj$*S)BpRQ;amN;1IsTP|7YO-2O3%Kliu$9b^D6%t>+I-u9!A^Vs#Re$OHYy+n?_L zGFjlmAO47{bz2T8pWgT;s?g=`X)e3TSL4fUtsm+}uKfKhTjiO~lZx$tDC~Upo7rLFC`#{|wtCHnq;~N9!Ca*&Gc%e7UXe z|UI)87v_qKV<%+o$Bxu?49N!*Qw z&3|ULAKrWF{^YMOPNr+Rud0r0`a11k$ILhU3%(wCWY_wA|EldiA7x(O=8H|8s1=o@ z7k+l;yuW70R!xX~_#aWh_87mYo@+KC z>E4F)=ZPz0e<|+W?sev}?d){Rt=r43lDo7MB`F8mA;+r{>^k)88R^{+I zf8FI?O+r&8&)|-G5_&MdJ}rIPZ>gdSOnVkz`a9`h+V3T2j+8N9`IQ)cq_*^ZV`$#; zhd<5Cq__QkCbHG%*8I7Gvb)~5URda4Vttc8!4ZKjJ<#c!Rjb3cc3#=Hkr{V}ZVPMqi?(lfwT*qg z<<5C_?e3kI-Isz5PYDYfD%H9vI<<0~mRv8e`9A}f@84N@u5U6pR>K+}-oG#9ZJKg- z&F!4-+2g)@O^KpS313I$>8BZ9mx2~5e3kf` z_`PV|(r+R`TP6f8@K9)0XfkJ!Jt*DW*cTCJF0peF-@yzve~JGLn=B8-T?A#3!r%JJ z-$d#>{xk3h{oZ}m{;kdNujlHviM3C88T)nRwut3^=g(ALNiyH^P(snbf?wNuTIm|q zcA2{&LH^~lcTMI=43hHyJoV$ka*3aX8PUnJM4#Mx_t`1f;jS@DNhSB96Gl=~_WoI1 zwKw?RnMtS3fAS=o|5Dtu*KFZiA$gXkPve3X1m}JGnZDo_OYZh%vY~Ucm$3DEz4TKx zofmENDk@6unV02}{RygG;`dhn`Ly=z?Q3ztTa$8qypA%J>Kv1GQ>^q6Y)WQtc*Odo z^3KQdxy#>9j<~S$2c9OT8iOiz#b@vlu_V*$~umN7Yg3 zrR|b@!F}p{rt2mYO?h}dPxa4rgImjQMZe~~)xns`=ajdnT~{5C zSdyVv9)0zB=1QAQw}NKwYH%|;?#@|tp=fHDu~t~cs@pzZ>+ZQ{+AbIU6S@8c*ILnK zC99Fz31m+f4{$^>8UFJ z(aLcC*#*r!pQERz9-VtN>5qHA`nzA-XPNkl{CyX!)~i|W$hml)faL5`p`oFCTS8Cn zwAW(cn7jP_xBEhk@fUb9Sf3cb$i5Y`{D5lH(tC@Z>p!$i@ew(=`}DR)W-QZIOX>)e=e>-0X* z0K%FtWmD3;P1Np43HB^pe!|E3bFtK#s_bpeA=9I;-p<^4UT0g1Ti$YxV-YVNDl{K0 zk7_*npFt=7{nxsFFHe>K49eh9-HCJVYDQkV>}d1SUX7=7?&J60@ALec%N)OPS@8Vx zYwxGsxXG|jq+2BT^bfm4LsbLaPj5H$uBp&{|K`l6`^kP$43R5dN3WT+J?q$Q(Z@2e z(K}aPTp7su;(O6t4u4tZMRG+6reEsBOYVO>^`4uD@zbG?JC2?eDC6uYS+BWmtM${M zlGiKyt@gORX;Ef*$=q;B7&gj3gFWi$Ivu4=r&mQjS-WZHAw!>v7xT|KS{^t0`Rs6s z1z)q;#l-FVo~UOkXa3%}&E(&llh;btWk#OU64h5&GG&3?exbfXg(Yo=<|>A%o`_w< z(%pKYR?<1g?)tMAyH#%`N2^WDx)kw7?CaDJ&Md!Fk+fqR=LF8NHN0fd(~dil74-5` z@2juDzm2?g_CGP~x&LD4nNN=e*6F;?-diR7w)nJUuW-qM#N{VKG|oN$B>BXAPN~yX zwTFe)In~0}iCbq~w(<{snRBe%_lZPg>2hZ;o`q~5L>gH=Jw3SvI6IWm%nv?U$hT?l zi|^KdSvl_C)zyEq|7Q4)WZmBo5H=WNQ%_j?A znd36xwLM{;@I?jtR$o=mtW4Q+f@Lb>o?pT@`h6-s%ytd_dN=b{scW{E(4}cyT#ggA zs6SP8Qgq*PCcpoV%q>yt&Z~T(xik9ZV)djWug$uW9rB)`X2YufQjW8;3?w3nT0$X| z2wA)6BBeLWt?&rj{9o7ZZx^icJO0tLp>(}xOVJsnjW_ok zeW#Wwa&`SS?N!;XS}Xf~`g5ETU!3bQSth&DB2j{eg@v&JxoI$ChEk%$g_ZZWE#DNH zb9_eM_xfAsHV3YCn_thY)~hmm#-W%G9d0w8OMczGy5d#a$32^pwhFELSiSWMXfQxX z$vC0I4>TCSY`B=?NHIfaUBc_f>?+@v7QN=PM+( z`DA-+&D5Rtn`zrBmMu^0HW??gq%}MfZYXJ(bhnk;W=FuQbm@o};rlM%=Cv}q*!RRu z%3FS6(5c~!4no}GfvX1=xkTX25m$F!}Xmo+b~H@T3YxFtqt z_rpb#=AJs0azp*JYy#iuV4c(YvqQg5otc(nest=lDh{m~eU&^GpSQVpNC}u+ZnyX# z$P8cYX1aY-;kXUEIA(RVL3-pS{UYVwR!FV|RlO zQ)8x;AKqtlfAUw3llhDPt&4vke{N2$|03(x;rc%g*}J`yHkY@;}3pgw;*)5i{kV*ZVwEbZX`5 z=8Zu#c$%JMl+R;d{Wm|VQuM`thSk%5uzz08!H=*}yvEAL^UvvjNui#r4lh13|IGep z3aeXEr|7P}8EC9)+V*4Zwz$;`R#jO(*{q5*3&wKdnD&7`3z1eT%!18=dL<>>pAu`x zjxMxueQBM3RC6tC5P6xJC{m~?{?>UYYb>&F)0Q)*HH#x7o-dg9H`7z)nM~qs;Y*y0 zh5amy+c^cek`hZ)=YDwKv;E0mC!-5I^9=tpq!hR8UF23UIXvR3|H{Q1A_OiubUjy` z%z2@AB1d1LpsbRLs^fxZnUzy*K237;loo!)Utnvv&|>q^S$ew{ZS`Aj@+xe4(5A2{ zjEvm=C!=~!wP$qRXMMx^r|i%6f0L5rqOwYRtKZxz~m^v1EN&vyna zTj`f+{jl9hccGA`9rTtc<&R8)Bm);#O-O1yT=gjSO+1fH+gfAJYOrBBL zp5xDGl9YLzt>B>&+b;LF$Jh02{G*iinD@b}fL=)(=qma7`?oz@nKbXz*O_Ty(@k^x zUOTd~WU9=eFbvYe~!&mlI z*QB~@yb=;Ud$&H!ur0tY^k~Ss`8Sq_RsO!RvDet-n7~<%&iE_uZgvIeOjvuBD@tsO zaIj~kSIK56wZ7y&@Pdim;XzrGC&ns%@NU!+Qo3(4ai_v};caZ2c4;j zZFpqkjEB~)jBWEZb&jS5?@qf4p8C=RPkpIA72NDqFw3&fg2lL@ttrpgf7!*W=RG*) zE?M#S$DQLjbrn~(mTh@t>@?-q^jRD>rv)x~vM|lw$IBR?-}`Nn^)U(mf0w>k2i~6% zx4!^3$O;wb-yFmhQUz@5^Uj^B*mW`o9$xZ!78N zbU3O#mAkC2buDXF$NLPvX(`L+NS?KEDCXdswEf0xvBJ~-Q?~Z09~an}tEnrR>9)Mm z!C|Sw4*Oo`6$0k z6w5n%bA91ykDZH)HpT`=zPjX-lrlRhna3?haFNa1i3cSdl$02>|83p=_DJ01jeml4 zk5wNGk9U7q?)#rXX8QLfcT+WYM{c{ZUOZXK_H5i%s|+9a-BWHH^LW8)@OkQbcs*4Z zrf2$6Yi*~F*tfVzB>{6zzn$^Ig~8cEMc{%$oWI?*SaX{N6JL4A&leN9gw-p-du4N$D1TiZ{rau z%FcGXo%LPf(Tne9+KYo0aM|8XUdZ`)L5cIZU{-0?wYOd^4?D53%y7ruZ4R@RT7CY$ z(Mn*>U4zTBTYAn;PC2_aa(8OeflaIC>RR$FnsDQw;v&l<7kC*|g9BGQxjwm4vr=Ws zD%Qgn{@#&)aaMAr(XL7Jet-FXsb$~Apl&|~hC-D|UI`55DsR}>RHUHmP9;4}6?F}L zPhF|pr)nOZljj~euca#4>Erq6@RIU|Z_0nx<(s|N5;gnu_Q%T**W*ik-rwf!mbsX< zan2?aaqG3OmR%02O!X+7y@Ca_#&^Pbj`iX{<98`#_a;ZVu3j4y{f6t-3ZZ)`pf(A& zSAfi+0=@}C3m-b2*AuatdF+~%==&|-4*T_NN!}^gHbFlt*7D8PEN3s?Ry`hxjsWTT zkKXUo{(5cGvala1(ZyDmXC9Y2J#~Su_|$usra$qV;J9OgkZ$t^W`;?vT*BNE@1+!{ zzIWew|AoqV4%1_H)u+F1z1Ld3?z+u|X{S2w8gRV$BpeKzu6DRSxq0@}_-(62cP;E& zpW^Q}%O^3_EHh9d-=fi>l+G`+p-S$3M0sxFS1x;=y3}OtT0Z;O$o`|( zf4Ob+fs9gh&u_Z-@637KZzfN<#LH&=XE5FRXZ^1}){5I-zt8)Z{YCe&1DA+K_>Ryd zp_T3Ko%S>9&jc*=@!{GKegEdnr~8i0*}|~UX~v8e@rpVL9oz7bM5h+bHFgW#ANHSF zWB;#Re%+emk6!;(j#ww65isq?tr;z|rf9fF>HoF=RR8W+=r6VUtM%vqGdw)jkg$D^ zxAE!Pjh0*m(Dtl|@|@Hht)*Ndnw`C&8X#MpL=!#tc>A8%yO9yxJ9r9`>;Hcf067mp Ar2qf` 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' }} /> 3~M9S&0}C-U@Q)DcVbv~PUa;81MhE7 z7srr_xVLxT7TrE3)ROq>RDaP^JGIGOtCSil7sxXAeEqe){qx1kwwt*FF5T|G{q{KT zzrP><@r7-_ZGCIvW@)wM=km7CzV^|6jq_hwkBL6ZKVRNgYrcCs%cir(Gns^5^GoXQT+`!8y> zzs{`wckf^Bm%m^Cha5R?{_^s_FMf-KS6gk9Ubn;N`(xe4Jz>sMI5=x}=X0i{FLqhe zQs=JFs4{__DR+X|34KMUBUbGZ`%k;Q=i9Vn;GY}HPO4pzcqMH z>7(qJujc<^b$kOlSN&VoxuEy*a?@G!&GM|?mDq2|@7nxmQQ_5irkjo-(&oM)xpzwL zux$D)vRe5XpMgc!vd&o!+Zw}THsscM`YUig>2m=2-NB*rf}nT$$60e9-C>J7Jo7$_ z`h?sCqSKF>)&8nry6IT5OYzqfkH=Y$cQvo^bSunjUi0t{+s%XdRRucRR`M=lc%>3v zf7#{2$!lyE`PSO+R z`@1u$#AoDfI+oiE3SqAd)t+C$i_|7ej$_ceu-;3#?7EeS{oEB% z?Z@V>OSW3~sOY(IVOtO1pK~`}Hf>dmYJax;$fZA5{5}UOWyngE@9KNGqsyBq@QHYT zl9$BR%Uh;$aHc%h43l#EoLVamn>^tchEGC>89fr z>l3%s=BTau6}+2eQ}Yz2$qqs%WE;&qq@Of42syqFx;#1N@5;G+UPqT&cPQPLeIgL# zb;a55V8mlJP(ppuxgbK}mvHMV0hhWHDZkF{VY}|Rq^#=u#l?Ncu2z}azdc@37uEhL zn~~2yZPGcpT726FaHq{uM1qkGRH5V@!Fp$4O|PZt#-Na zM?`$B^WV(9ulx%y{>!TC@@BgEatYH4-asXl32)m}1Y|9(%eWQ@88qfFuVr?;y|UT) z_v)XS^|uvr1)hF7?8mvnPxyoqqiDulgIodEExbkU3wSqaB+Ol5r5Sp_tJv0Sh5g#_ zznXeh45s!+RU1_Vo))>UN%x3dn(%hsv*p53>OFosbEnLj|7cMuROPMsv;Kvd&08F& zoW5V&{GypNbi<;x|KjfYy$-cr8~%6Y++$Zc*Uo;ps&{4ShxLC08J0^gDqO$ma@AG8 z*8%le%L}JQaJo#qzwvV0o?Um9c;?%!`x+-|-l!uKCB59GqSShA_}eXUUze9jnS)Ca zFA3EJxAs=uVVUG0bVArv9aLTjow(2OMdbC8#tVYk(v5PT^@IY>KMB97`4MJ?b&{@7 z)h@+bJ66Bh61VlT)s^|TGVkQ8Pmup1TL1UmsS@WB*6g}l3163g&5Ef#xUA*cpDXuQ ze|LTz04iL47qVA)333CNk^Iqg!>ypvDhF1qH!cz2NW10J!$sCKh z>b|yZalEFsFE8xwi&NTLvzzCb2u$IS-FS^<`H}G5ER!6pl58#6z{Q-<33jHcD;JIP z%O`AAzF1=a<;9XRaM}Ch4DX^-bLy;?POiD-r~aViPGDuLO~oN|{;l$4vTVO_IZ()hY38#lhwq2Fx4cXOb3&LZ%XYeMSubU9*Kvd6>a=jv z3e)%Xb^CZPvCVuI0xIMh8`dq+d%54GegBm6G5N{wQ_Kl@MDvow}#@qH^m)A_y=S*S0$g(KJTY;Y?F7~CB;1*D^|Bhpe<1K~# zPip(*I!h8(+E3lecLf}`o5knZ9*wH*v$x$?vdB-KvAXMO@;&GBm6=Shy!;iir~iA| zcvWzjTXsf#J|od0j8yy~GZT32@55;*JMpLF?WtY~;)p!Hhk z_a3j_d(=tIuF(=aanoP<>+&+rl-x<#_pkVIu2l)GcmH(x#*^SquX92t^nAQn-X8z* z;KIKwS+Ah4{peLfu=QHyMjfI0 zSq+zY=biKKG?fKgsSHlEyB73Z*Gu6xgO=X&3x8gNl7{6Gxw|c#DbiuK zGgrjAuW|nJH|LIQ3hx0>AuG^)OW}D=o7ss)22-!*3bb77v}ma zbcHZkwoDN+ZTuqgy076=>aBI&zZ4_OJ>y$mUp^lHufz0jc;5P1KQ2!O6-!<$stcYi zFS}eQDsC&adVl7eQYi~|}Ailqh;-u)6)t+{@`QF@E#x zXH(NAJCsfEQt(<>{6^>m-#M#~TYX>YH)BU%a@7He_vi)vOMB=^{=V^pi&H!SwG%fC>(FQdcRqRQdQsGROyso@0ZMH zzW!XpD}yzvbxHiwfS4~Qmi^S!lL~(wI%oOk;5j?{ST^yl6bfj(c1$zSbblsO)?Q8* z>$*vEg&d`qvxFSd;NeV>);#K3cx_+r|5i=kf?IlxIm)YcXfV!i&uU~Xr z6@1<2z;yGOqjbv0kDL6I=H_^X*fRO~XRHnH-*ow>=D8Sma6|g$^iWVy@8DoA?jI-f zG*^Hn>#9%VwX5shB~*K^EqA&4M?}1L^;fNP5IvyMHFrXz@-(er_4Z$vJy(`Vg}*gf z=cm)VS~&i1X5HmN)BTy&Yp1_gn3ceE)6GkIk%hl-=;zsetH1i)&fLqHBKXtngi?ce ztxe+`mKr8Y&KD02I8!7p*p_oXQCZM_SY<+@gYb#@93||LDq+*V=)LR~v3mMAcS3N& zxA@D8c`t5RuD!gSNofpL!7f@4>Y`93ed$`S%s1BzK@M%7ifR4167U}mh< zFSyDQ;^co@71aC)VfB9#G9#RKMqh;NMyn)S!2(df?LLS^n`CG|iY~ US^HoG0|Nttr>mdKI;Vst09V}4mH+?% literal 0 HcmV?d00001 diff --git a/src/pages/image/png/create-transparent/create-transparent.e2e.spec.ts b/src/pages/image/png/create-transparent/create-transparent.e2e.spec.ts new file mode 100644 index 0000000..1d0c504 --- /dev/null +++ b/src/pages/image/png/create-transparent/create-transparent.e2e.spec.ts @@ -0,0 +1,40 @@ +import { expect, test } from '@playwright/test'; +import { Buffer } from 'buffer'; +import path from 'path'; +import Jimp from 'jimp'; + +test.describe('Create transparent PNG', () => { + 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' }} /> 3~M9S&0}C-U@Q)DcVbv~PUa;81MhE7 z7srr_xVLxT7TrE3)ROq>RDaP^JGIGOtCSil7sxXAeEqe){qx1kwwt*FF5T|G{q{KT zzrP><@r7-_ZGCIvW@)wM=km7CzV^|6jq_hwkBL6ZKVRNgYrcCs%cir(Gns^5^GoXQT+`!8y> zzs{`wckf^Bm%m^Cha5R?{_^s_FMf-KS6gk9Ubn;N`(xe4Jz>sMI5=x}=X0i{FLqhe zQs=JFs4{__DR+X|34KMUBUbGZ`%k;Q=i9Vn;GY}HPO4pzcqMH z>7(qJujc<^b$kOlSN&VoxuEy*a?@G!&GM|?mDq2|@7nxmQQ_5irkjo-(&oM)xpzwL zux$D)vRe5XpMgc!vd&o!+Zw}THsscM`YUig>2m=2-NB*rf}nT$$60e9-C>J7Jo7$_ z`h?sCqSKF>)&8nry6IT5OYzqfkH=Y$cQvo^bSunjUi0t{+s%XdRRucRR`M=lc%>3v zf7#{2$!lyE`PSO+R z`@1u$#AoDfI+oiE3SqAd)t+C$i_|7ej$_ceu-;3#?7EeS{oEB% z?Z@V>OSW3~sOY(IVOtO1pK~`}Hf>dmYJax;$fZA5{5}UOWyngE@9KNGqsyBq@QHYT zl9$BR%Uh;$aHc%h43l#EoLVamn>^tchEGC>89fr z>l3%s=BTau6}+2eQ}Yz2$qqs%WE;&qq@Of42syqFx;#1N@5;G+UPqT&cPQPLeIgL# zb;a55V8mlJP(ppuxgbK}mvHMV0hhWHDZkF{VY}|Rq^#=u#l?Ncu2z}azdc@37uEhL zn~~2yZPGcpT726FaHq{uM1qkGRH5V@!Fp$4O|PZt#-Na zM?`$B^WV(9ulx%y{>!TC@@BgEatYH4-asXl32)m}1Y|9(%eWQ@88qfFuVr?;y|UT) z_v)XS^|uvr1)hF7?8mvnPxyoqqiDulgIodEExbkU3wSqaB+Ol5r5Sp_tJv0Sh5g#_ zznXeh45s!+RU1_Vo))>UN%x3dn(%hsv*p53>OFosbEnLj|7cMuROPMsv;Kvd&08F& zoW5V&{GypNbi<;x|KjfYy$-cr8~%6Y++$Zc*Uo;ps&{4ShxLC08J0^gDqO$ma@AG8 z*8%le%L}JQaJo#qzwvV0o?Um9c;?%!`x+-|-l!uKCB59GqSShA_}eXUUze9jnS)Ca zFA3EJxAs=uVVUG0bVArv9aLTjow(2OMdbC8#tVYk(v5PT^@IY>KMB97`4MJ?b&{@7 z)h@+bJ66Bh61VlT)s^|TGVkQ8Pmup1TL1UmsS@WB*6g}l3163g&5Ef#xUA*cpDXuQ ze|LTz04iL47qVA)333CNk^Iqg!>ypvDhF1qH!cz2NW10J!$sCKh z>b|yZalEFsFE8xwi&NTLvzzCb2u$IS-FS^<`H}G5ER!6pl58#6z{Q-<33jHcD;JIP z%O`AAzF1=a<;9XRaM}Ch4DX^-bLy;?POiD-r~aViPGDuLO~oN|{;l$4vTVO_IZ()hY38#lhwq2Fx4cXOb3&LZ%XYeMSubU9*Kvd6>a=jv z3e)%Xb^CZPvCVuI0xIMh8`dq+d%54GegBm6G5N{wQ_Kl@MDvow}#@qH^m)A_y=S*S0$g(KJTY;Y?F7~CB;1*D^|Bhpe<1K~# zPip(*I!h8(+E3lecLf}`o5knZ9*wH*v$x$?vdB-KvAXMO@;&GBm6=Shy!;iir~iA| zcvWzjTXsf#J|od0j8yy~GZT32@55;*JMpLF?WtY~;)p!Hhk z_a3j_d(=tIuF(=aanoP<>+&+rl-x<#_pkVIu2l)GcmH(x#*^SquX92t^nAQn-X8z* z;KIKwS+Ah4{peLfu=QHyMjfI0 zSq+zY=biKKG?fKgsSHlEyB73Z*Gu6xgO=X&3x8gNl7{6Gxw|c#DbiuK zGgrjAuW|nJH|LIQ3hx0>AuG^)OW}D=o7ss)22-!*3bb77v}ma zbcHZkwoDN+ZTuqgy076=>aBI&zZ4_OJ>y$mUp^lHufz0jc;5P1KQ2!O6-!<$stcYi zFS}eQDsC&adVl7eQYi~|}Ailqh;-u)6)t+{@`QF@E#x zXH(NAJCsfEQt(<>{6^>m-#M#~TYX>YH)BU%a@7He_vi)vOMB=^{=V^pi&H!SwG%fC>(FQdcRqRQdQsGROyso@0ZMH zzW!XpD}yzvbxHiwfS4~Qmi^S!lL~(wI%oOk;5j?{ST^yl6bfj(c1$zSbblsO)?Q8* z>$*vEg&d`qvxFSd;NeV>);#K3cx_+r|5i=kf?IlxIm)YcXfV!i&uU~Xr z6@1<2z;yGOqjbv0kDL6I=H_^X*fRO~XRHnH-*ow>=D8Sma6|g$^iWVy@8DoA?jI-f zG*^Hn>#9%VwX5shB~*K^EqA&4M?}1L^;fNP5IvyMHFrXz@-(er_4Z$vJy(`Vg}*gf z=cm)VS~&i1X5HmN)BTy&Yp1_gn3ceE)6GkIk%hl-=;zsetH1i)&fLqHBKXtngi?ce ztxe+`mKr8Y&KD02I8!7p*p_oXQCZM_SY<+@gYb#@93||LDq+*V=)LR~v3mMAcS3N& zxA@D8c`t5RuD!gSNofpL!7f@4>Y`93ed$`S%s1BzK@M%7ifR4167U}mh< zFSyDQ;^co@71aC)VfB9#G9#RKMqh;NMyn)S!2(df?LLS^n`CG|iY~ US^HoG0|Nttr>mdKI;Vst09V}4mH+?% literal 0 HcmV?d00001 From 48cc2f21631f5c7c145d55a11e437bd7b9280a7b Mon Sep 17 00:00:00 2001 From: Habib 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 @@