diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 2cc7269..82966df 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -4,21 +4,18 @@
-
-
-
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
@@ -341,30 +338,6 @@
-
-
- 1720730102816
-
-
-
- 1720730102817
-
-
-
- 1720913013183
-
-
-
- 1720913013183
-
-
-
- 1720913810733
-
-
-
- 1720913810733
-
1720914492812
@@ -733,7 +706,31 @@
1741417920442
-
+
+
+ 1741419142510
+
+
+
+ 1741419142510
+
+
+
+ 1741419188990
+
+
+
+ 1741419188990
+
+
+
+ 1741419527557
+
+
+
+ 1741419527557
+
+
@@ -792,9 +789,6 @@
-
-
-
@@ -817,7 +811,10 @@
-
+
+
+
+
diff --git a/package-lock.json b/package-lock.json
index c1b655f..f516855 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,7 @@
"dependencies": {
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5",
+ "@jimp/types": "^1.6.0",
"@mui/icons-material": "^5.15.20",
"@mui/material": "^5.15.20",
"@playwright/test": "^1.45.0",
@@ -1540,6 +1541,7 @@
"version": "0.22.12",
"resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.22.12.tgz",
"integrity": "sha512-aeI64HD0npropd+AR76MCcvvRaa+Qck6loCOS03CkkxGHN5/r336qTM5HPUdHKMDOGzqknuVPA8+kK1t03z12g==",
+ "license": "MIT",
"dependencies": {
"@jimp/utils": "^0.22.12",
"bmp-js": "^0.1.0"
@@ -1575,6 +1577,7 @@
"version": "0.22.12",
"resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.22.12.tgz",
"integrity": "sha512-y6BFTJgch9mbor2H234VSjd9iwAhaNf/t3US5qpYIs0TSbAvM02Fbc28IaDETj9+4YB4676sz4RcN/zwhfu1pg==",
+ "license": "MIT",
"dependencies": {
"@jimp/utils": "^0.22.12",
"gifwrap": "^0.10.1",
@@ -1588,6 +1591,7 @@
"version": "0.22.12",
"resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.22.12.tgz",
"integrity": "sha512-Rq26XC/uQWaQKyb/5lksCTCxXhtY01NJeBN+dQv5yNYedN0i7iYu+fXEoRsfaJ8xZzjoANH8sns7rVP4GE7d/Q==",
+ "license": "MIT",
"dependencies": {
"@jimp/utils": "^0.22.12",
"jpeg-js": "^0.4.4"
@@ -1881,6 +1885,7 @@
"version": "0.22.12",
"resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.22.12.tgz",
"integrity": "sha512-Mrp6dr3UTn+aLK8ty/dSKELz+Otdz1v4aAXzV5q53UDD2rbB5joKVJ/ChY310B+eRzNxIovbUF1KVrUsYdE8Hg==",
+ "license": "MIT",
"dependencies": {
"@jimp/utils": "^0.22.12",
"pngjs": "^6.0.0"
@@ -1893,6 +1898,7 @@
"version": "0.22.12",
"resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.22.12.tgz",
"integrity": "sha512-E1LtMh4RyJsoCAfAkBRVSYyZDTtLq9p9LUiiYP0vPtXyxX4BiYBUYihTLSBlCQg5nF2e4OpQg7SPrLdJ66u7jg==",
+ "license": "MIT",
"dependencies": {
"utif2": "^4.0.1"
},
@@ -1901,19 +1907,15 @@
}
},
"node_modules/@jimp/types": {
- "version": "0.22.12",
- "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.22.12.tgz",
- "integrity": "sha512-wwKYzRdElE1MBXFREvCto5s699izFHNVvALUv79GXNbsOVqlwlOxlWJ8DuyOGIXoLP4JW/m30YyuTtfUJgMRMA==",
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@jimp/types/-/types-1.6.0.tgz",
+ "integrity": "sha512-7UfRsiKo5GZTAATxm2qQ7jqmUXP0DxTArztllTcYdyw6Xi5oT4RaoXynVtCD4UyLK5gJgkZJcwonoijrhYFKfg==",
+ "license": "MIT",
"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"
+ "zod": "^3.23.8"
},
- "peerDependencies": {
- "@jimp/custom": ">=0.3.5"
+ "engines": {
+ "node": ">=18"
}
},
"node_modules/@jimp/utils": {
@@ -3846,7 +3848,8 @@
"node_modules/bmp-js": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz",
- "integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw=="
+ "integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==",
+ "license": "MIT"
},
"node_modules/brace-expansion": {
"version": "2.0.1",
@@ -5647,6 +5650,7 @@
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.10.1.tgz",
"integrity": "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw==",
+ "license": "MIT",
"dependencies": {
"image-q": "^4.0.0",
"omggif": "^1.0.10"
@@ -6019,6 +6023,7 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/image-q/-/image-q-4.0.0.tgz",
"integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==",
+ "license": "MIT",
"dependencies": {
"@types/node": "16.9.1"
}
@@ -6026,7 +6031,8 @@
"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=="
+ "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==",
+ "license": "MIT"
},
"node_modules/import-fresh": {
"version": "3.3.0",
@@ -6598,6 +6604,23 @@
"regenerator-runtime": "^0.13.3"
}
},
+ "node_modules/jimp/node_modules/@jimp/types": {
+ "version": "0.22.12",
+ "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.22.12.tgz",
+ "integrity": "sha512-wwKYzRdElE1MBXFREvCto5s699izFHNVvALUv79GXNbsOVqlwlOxlWJ8DuyOGIXoLP4JW/m30YyuTtfUJgMRMA==",
+ "license": "MIT",
+ "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/node_modules/regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
@@ -6628,7 +6651,8 @@
"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=="
+ "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==",
+ "license": "BSD-3-Clause"
},
"node_modules/js-tokens": {
"version": "4.0.0",
@@ -7448,7 +7472,8 @@
"node_modules/pako": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
- "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
+ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
+ "license": "(MIT AND Zlib)"
},
"node_modules/parent-module": {
"version": "1.0.1",
@@ -7710,6 +7735,7 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz",
"integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==",
+ "license": "MIT",
"engines": {
"node": ">=12.13.0"
}
@@ -9578,6 +9604,7 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/utif2/-/utif2-4.1.0.tgz",
"integrity": "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w==",
+ "license": "MIT",
"dependencies": {
"pako": "^1.0.11"
}
@@ -10186,6 +10213,15 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
+ },
+ "node_modules/zod": {
+ "version": "3.24.2",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz",
+ "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
}
}
}
diff --git a/package.json b/package.json
index b9e8a99..a5f6b94 100644
--- a/package.json
+++ b/package.json
@@ -27,6 +27,7 @@
"dependencies": {
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5",
+ "@jimp/types": "^1.6.0",
"@mui/icons-material": "^5.15.20",
"@mui/material": "^5.15.20",
"@playwright/test": "^1.45.0",
diff --git a/src/components/options/ToolOptions.tsx b/src/components/options/ToolOptions.tsx
index f0114c6..b737ebf 100644
--- a/src/components/options/ToolOptions.tsx
+++ b/src/components/options/ToolOptions.tsx
@@ -5,7 +5,6 @@ import React, { ReactNode, RefObject, useContext, useEffect } from 'react';
import { Formik, FormikProps, FormikValues, useFormikContext } from 'formik';
import ToolOptionGroups, { ToolOptionGroup } from './ToolOptionGroups';
import { CustomSnackBarContext } from '../../contexts/CustomSnackBarContext';
-import * as Yup from 'yup';
export type UpdateField = (field: Y, value: T[Y]) => void;
@@ -26,6 +25,7 @@ const FormikListenerComponent = ({
compute(values, input);
} catch (exception: unknown) {
if (exception instanceof Error) showSnackBar(exception.message, 'error');
+ else console.error(exception);
}
}, [values, input]);
diff --git a/src/pages/tools/image/png/change-opacity/index.tsx b/src/pages/tools/image/png/change-opacity/index.tsx
new file mode 100644
index 0000000..43fc3ef
--- /dev/null
+++ b/src/pages/tools/image/png/change-opacity/index.tsx
@@ -0,0 +1,87 @@
+import React, { useEffect, useState } from 'react';
+import ToolFileInput from '@components/input/ToolFileInput';
+import ToolFileResult from '@components/result/ToolFileResult';
+import { changeOpacity } from './service';
+import ToolContent from '@components/ToolContent';
+import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
+import { CardExampleType } from '@components/examples/ToolExamples';
+import { ToolComponentProps } from '@tools/defineTool';
+import { updateNumberField } from '@utils/string';
+
+type InitialValuesType = {
+ opacity: number;
+};
+
+const initialValues: InitialValuesType = {
+ opacity: 1
+};
+
+const exampleCards: CardExampleType[] = [
+ {
+ title: 'Semi-transparent PNG',
+ description: 'Make an image 50% transparent',
+ sampleOptions: {
+ opacity: 0.5
+ },
+ sampleResult: ''
+ },
+ {
+ title: 'Slightly Faded PNG',
+ description: 'Create a subtle transparency effect',
+ sampleOptions: {
+ opacity: 0.8
+ },
+ sampleResult: ''
+ }
+];
+
+export default function ChangeOpacity({ title }: ToolComponentProps) {
+ const [input, setInput] = useState(null);
+ const [result, setResult] = useState(null);
+
+ const compute = (values: InitialValuesType, input: any) => {
+ if (input) {
+ changeOpacity(input, values.opacity).then(setResult);
+ }
+ };
+ return (
+
+ }
+ resultComponent={
+
+ }
+ initialValues={initialValues}
+ exampleCards={exampleCards}
+ getGroups={({ values, updateField }) => [
+ {
+ title: 'Opacity Settings',
+ component: (
+
+ updateNumberField(val, 'opacity', updateField)
+ }
+ type="number"
+ inputProps={{ step: 0.1, min: 0, max: 1 }}
+ />
+ )
+ }
+ ]}
+ compute={compute}
+ />
+ );
+}
diff --git a/src/pages/tools/image/png/change-opacity/meta.ts b/src/pages/tools/image/png/change-opacity/meta.ts
new file mode 100644
index 0000000..1be290d
--- /dev/null
+++ b/src/pages/tools/image/png/change-opacity/meta.ts
@@ -0,0 +1,12 @@
+import { defineTool } from '@tools/defineTool';
+import { lazy } from 'react';
+
+export const tool = defineTool('png', {
+ name: 'Change PNG Opacity',
+ path: 'change-opacity',
+ icon: 'material-symbols:opacity',
+ description: 'Easily adjust the transparency of your PNG images. Simply upload your PNG file, use the slider to set the desired opacity level between 0 (fully transparent) and 1 (fully opaque), and download the modified image.',
+ shortDescription: 'Adjust transparency of PNG images',
+ keywords: ['opacity', 'transparency', 'png', 'alpha'],
+ component: lazy(() => import('./index'))
+});
diff --git a/src/pages/tools/image/png/change-opacity/service.ts b/src/pages/tools/image/png/change-opacity/service.ts
new file mode 100644
index 0000000..217e4c4
--- /dev/null
+++ b/src/pages/tools/image/png/change-opacity/service.ts
@@ -0,0 +1,39 @@
+export async function changeOpacity(
+ file: File,
+ opacity: number
+): Promise {
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.onload = (event) => {
+ const img = new Image();
+ img.onload = () => {
+ const canvas = document.createElement('canvas');
+ const ctx = canvas.getContext('2d');
+ if (!ctx) {
+ reject(new Error('Canvas context not supported'));
+ return;
+ }
+
+ canvas.width = img.width;
+ canvas.height = img.height;
+
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ ctx.globalAlpha = opacity;
+ ctx.drawImage(img, 0, 0);
+
+ canvas.toBlob((blob) => {
+ if (blob) {
+ const newFile = new File([blob], file.name, { type: 'image/png' });
+ resolve(newFile);
+ } else {
+ reject(new Error('Failed to generate image blob'));
+ }
+ }, 'image/png');
+ };
+ img.onerror = () => reject(new Error('Failed to load image'));
+ img.src = event.target?.result;
+ };
+ reader.onerror = () => reject(new Error('Failed to read file'));
+ reader.readAsDataURL(file);
+ });
+}
diff --git a/src/pages/tools/image/png/index.ts b/src/pages/tools/image/png/index.ts
index fabac34..17db8e7 100644
--- a/src/pages/tools/image/png/index.ts
+++ b/src/pages/tools/image/png/index.ts
@@ -2,10 +2,12 @@ import { tool as pngCompressPng } from './compress-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';
+import { tool as changeOpacity } from './change-opacity/meta';
export const pngTools = [
pngCompressPng,
pngCreateTransparent,
changeColorsInPng,
- convertJgpToPng
+ convertJgpToPng,
+ changeOpacity
];
diff --git a/src/pages/tools/list/index.ts b/src/pages/tools/list/index.ts
index 8a3c7bf..0ea2f7c 100644
--- a/src/pages/tools/list/index.ts
+++ b/src/pages/tools/list/index.ts
@@ -12,7 +12,7 @@ import { tool as listSort } from './sort/meta';
export const listTools = [
listSort,
- listUnwrap,
+ // listUnwrap,
listReverse,
listFindUnique,
listFindMostPopular,
diff --git a/src/pages/tools/string/reverse/meta.ts b/src/pages/tools/string/reverse/meta.ts
index dfef5d4..e5a0097 100644
--- a/src/pages/tools/string/reverse/meta.ts
+++ b/src/pages/tools/string/reverse/meta.ts
@@ -5,7 +5,8 @@ export const tool = defineTool('string', {
name: 'Reverse',
path: 'reverse',
icon: '',
- description: "World's simplest browser-based utility for reversing text. Input any text and get it instantly reversed, character by character. Perfect for creating mirror text, analyzing palindromes, or playing with text patterns. Preserves spaces and special characters while reversing.",
+ description:
+ "World's simplest browser-based utility for reversing text. Input any text and get it instantly reversed, character by character. Perfect for creating mirror text, analyzing palindromes, or playing with text patterns. Preserves spaces and special characters while reversing.",
shortDescription: 'Reverse any text character by character',
keywords: ['reverse'],
component: lazy(() => import('./index'))
diff --git a/src/typed/jimp.d.ts b/src/typed/jimp.d.ts
deleted file mode 100644
index 212e588..0000000
--- a/src/typed/jimp.d.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-declare module 'jimp' {
- class JimpImage {
- getPixelColor: (x: number, y: number) => number;
- }
-
- export function read(buffer: Buffer): Promise;
-}