diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 82966df..700387c 100644
--- a/.idea/workspace.xml
+++ b/.idea/workspace.xml
@@ -4,18 +4,10 @@
-
-
-
-
+
-
-
-
-
-
-
-
+
+
@@ -338,14 +330,6 @@
-
-
- 1720914492812
-
-
-
- 1720914492812
-
1720914702655
@@ -730,7 +714,15 @@
1741419527557
-
+
+
+ 1741423117739
+
+
+
+ 1741423117739
+
+
@@ -789,7 +781,6 @@
-
@@ -814,7 +805,8 @@
-
+
+
diff --git a/src/pages/tools/image/png/change-opacity/index.tsx b/src/pages/tools/image/png/change-opacity/index.tsx
index 43fc3ef..5de8f3b 100644
--- a/src/pages/tools/image/png/change-opacity/index.tsx
+++ b/src/pages/tools/image/png/change-opacity/index.tsx
@@ -7,13 +7,29 @@ import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
import { CardExampleType } from '@components/examples/ToolExamples';
import { ToolComponentProps } from '@tools/defineTool';
import { updateNumberField } from '@utils/string';
+import { Box } from '@mui/material';
+import SimpleRadio from '@components/options/SimpleRadio';
type InitialValuesType = {
opacity: number;
+ mode: 'solid' | 'gradient';
+ gradientType: 'linear' | 'radial';
+ gradientDirection: 'left-to-right' | 'inside-out';
+ areaLeft: number;
+ areaTop: number;
+ areaWidth: number;
+ areaHeight: number;
};
const initialValues: InitialValuesType = {
- opacity: 1
+ opacity: 0.5,
+ mode: 'solid',
+ gradientType: 'linear',
+ gradientDirection: 'left-to-right',
+ areaLeft: 0,
+ areaTop: 0,
+ areaWidth: 100,
+ areaHeight: 100
};
const exampleCards: CardExampleType[] = [
@@ -21,7 +37,14 @@ const exampleCards: CardExampleType[] = [
title: 'Semi-transparent PNG',
description: 'Make an image 50% transparent',
sampleOptions: {
- opacity: 0.5
+ opacity: 0.5,
+ mode: 'solid',
+ gradientType: 'linear',
+ gradientDirection: 'left-to-right',
+ areaLeft: 0,
+ areaTop: 0,
+ areaWidth: 100,
+ areaHeight: 100
},
sampleResult: ''
},
@@ -29,7 +52,29 @@ const exampleCards: CardExampleType[] = [
title: 'Slightly Faded PNG',
description: 'Create a subtle transparency effect',
sampleOptions: {
- opacity: 0.8
+ opacity: 0.8,
+ mode: 'solid',
+ gradientType: 'linear',
+ gradientDirection: 'left-to-right',
+ areaLeft: 0,
+ areaTop: 0,
+ areaWidth: 100,
+ areaHeight: 100
+ },
+ sampleResult: ''
+ },
+ {
+ title: 'Radial Gradient Opacity',
+ description: 'Apply a radial gradient opacity effect',
+ sampleOptions: {
+ opacity: 0.8,
+ mode: 'gradient',
+ gradientType: 'radial',
+ gradientDirection: 'inside-out',
+ areaLeft: 25,
+ areaTop: 25,
+ areaWidth: 50,
+ areaHeight: 50
},
sampleResult: ''
}
@@ -41,7 +86,7 @@ export default function ChangeOpacity({ title }: ToolComponentProps) {
const compute = (values: InitialValuesType, input: any) => {
if (input) {
- changeOpacity(input, values.opacity).then(setResult);
+ changeOpacity(input, values).then(setResult);
}
};
return (
@@ -64,20 +109,92 @@ export default function ChangeOpacity({ title }: ToolComponentProps) {
/>
}
initialValues={initialValues}
- exampleCards={exampleCards}
+ // exampleCards={exampleCards}
getGroups={({ values, updateField }) => [
{
title: 'Opacity Settings',
component: (
-
- updateNumberField(val, 'opacity', updateField)
- }
- type="number"
- inputProps={{ step: 0.1, min: 0, max: 1 }}
- />
+
+
+ updateNumberField(val, 'opacity', updateField)
+ }
+ type="number"
+ inputProps={{ step: 0.1, min: 0, max: 1 }}
+ />
+ updateField('mode', 'solid')}
+ checked={values.mode === 'solid'}
+ description={'Set the same opacity level for all pixels'}
+ title={'Apply Solid Opacity'}
+ />
+ updateField('mode', 'gradient')}
+ checked={values.mode === 'gradient'}
+ description={'Change opacity in a gradient'}
+ title={'Apply Gradient Opacity'}
+ />
+
+ )
+ },
+ {
+ title: 'Gradient Options',
+ component: (
+
+ updateField('gradientType', 'linear')}
+ checked={values.gradientType === 'linear'}
+ description={'Linear opacity direction'}
+ title={'Linear Gradient'}
+ />
+ updateField('gradientType', 'radial')}
+ checked={values.gradientType === 'radial'}
+ description={'Radial opacity direction'}
+ title={'Radial Gradient'}
+ />
+
+ )
+ },
+ {
+ title: 'Opacity Area',
+ component: (
+
+
+ updateNumberField(val, 'areaLeft', updateField)
+ }
+ type="number"
+ />
+
+ updateNumberField(val, 'areaTop', updateField)
+ }
+ type="number"
+ />
+
+ updateNumberField(val, 'areaWidth', updateField)
+ }
+ type="number"
+ />
+
+ updateNumberField(val, 'areaHeight', updateField)
+ }
+ type="number"
+ />
+
)
}
]}
diff --git a/src/pages/tools/image/png/change-opacity/service.ts b/src/pages/tools/image/png/change-opacity/service.ts
index 217e4c4..bea3a73 100644
--- a/src/pages/tools/image/png/change-opacity/service.ts
+++ b/src/pages/tools/image/png/change-opacity/service.ts
@@ -1,7 +1,15 @@
-export async function changeOpacity(
- file: File,
- opacity: number
-): Promise {
+interface OpacityOptions {
+ opacity: number;
+ mode: 'solid' | 'gradient';
+ gradientType: 'linear' | 'radial';
+ gradientDirection: 'left-to-right' | 'inside-out';
+ areaLeft: number;
+ areaTop: number;
+ areaWidth: number;
+ areaHeight: number;
+}
+
+export async function changeOpacity(file: File, options: OpacityOptions): Promise {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (event) => {
@@ -13,13 +21,14 @@ export async function changeOpacity(
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);
+ if (options.mode === 'solid') {
+ applySolidOpacity(ctx, img, options);
+ } else {
+ applyGradientOpacity(ctx, img, options);
+ }
canvas.toBlob((blob) => {
if (blob) {
@@ -31,9 +40,82 @@ export async function changeOpacity(
}, 'image/png');
};
img.onerror = () => reject(new Error('Failed to load image'));
- img.src = event.target?.result;
+ img.src = event.target?.result as string;
};
reader.onerror = () => reject(new Error('Failed to read file'));
reader.readAsDataURL(file);
});
}
+
+function applySolidOpacity(
+ ctx: CanvasRenderingContext2D,
+ img: HTMLImageElement,
+ options: OpacityOptions
+) {
+ ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
+ ctx.globalAlpha = options.opacity;
+ ctx.drawImage(img, 0, 0);
+}
+
+function applyGradientOpacity(
+ ctx: CanvasRenderingContext2D,
+ img: HTMLImageElement,
+ options: OpacityOptions
+) {
+ const { areaLeft, areaTop, areaWidth, areaHeight } = options;
+
+ ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
+ ctx.drawImage(img, 0, 0);
+
+ const gradient = options.gradientType === 'linear'
+ ? createLinearGradient(ctx, options)
+ : createRadialGradient(ctx, options);
+
+ ctx.fillStyle = gradient;
+ ctx.fillRect(areaLeft, areaTop, areaWidth, areaHeight);
+}
+
+function createLinearGradient(
+ ctx: CanvasRenderingContext2D,
+ options: OpacityOptions
+) {
+ const { areaLeft, areaTop, areaWidth, areaHeight } = options;
+ const gradient = ctx.createLinearGradient(
+ areaLeft,
+ areaTop,
+ areaLeft + areaWidth,
+ areaTop
+ );
+ gradient.addColorStop(0, `rgba(255,255,255,${options.opacity})`);
+ gradient.addColorStop(1, 'rgba(255,255,255,0)');
+ return gradient;
+}
+
+function createRadialGradient(
+ ctx: CanvasRenderingContext2D,
+ options: OpacityOptions
+) {
+ const { areaLeft, areaTop, areaWidth, areaHeight } = options;
+ const centerX = areaLeft + areaWidth / 2;
+ const centerY = areaTop + areaHeight / 2;
+ const radius = Math.min(areaWidth, areaHeight) / 2;
+
+ const gradient = ctx.createRadialGradient(
+ centerX,
+ centerY,
+ 0,
+ centerX,
+ centerY,
+ radius
+ );
+
+ if (options.gradientDirection === 'inside-out') {
+ gradient.addColorStop(0, `rgba(255,255,255,${options.opacity})`);
+ gradient.addColorStop(1, 'rgba(255,255,255,0)');
+ } else {
+ gradient.addColorStop(0, 'rgba(255,255,255,0)');
+ gradient.addColorStop(1, `rgba(255,255,255,${options.opacity})`);
+ }
+
+ return gradient;
+}