mirror of
https://github.com/iib0011/omni-tools.git
synced 2025-12-29 16:16:02 +00:00
chore: move from png to image-generic
This commit is contained in:
200
src/pages/tools/image/generic/change-opacity/index.tsx
Normal file
200
src/pages/tools/image/generic/change-opacity/index.tsx
Normal file
@@ -0,0 +1,200 @@
|
||||
import React, { useState } from 'react';
|
||||
import ToolImageInput from '@components/input/ToolImageInput';
|
||||
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';
|
||||
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: 0.5,
|
||||
mode: 'solid',
|
||||
gradientType: 'linear',
|
||||
gradientDirection: 'left-to-right',
|
||||
areaLeft: 0,
|
||||
areaTop: 0,
|
||||
areaWidth: 100,
|
||||
areaHeight: 100
|
||||
};
|
||||
|
||||
const exampleCards: CardExampleType<InitialValuesType>[] = [
|
||||
{
|
||||
title: 'Semi-transparent PNG',
|
||||
description: 'Make an image 50% transparent',
|
||||
sampleOptions: {
|
||||
opacity: 0.5,
|
||||
mode: 'solid',
|
||||
gradientType: 'linear',
|
||||
gradientDirection: 'left-to-right',
|
||||
areaLeft: 0,
|
||||
areaTop: 0,
|
||||
areaWidth: 100,
|
||||
areaHeight: 100
|
||||
},
|
||||
sampleResult: ''
|
||||
},
|
||||
{
|
||||
title: 'Slightly Faded PNG',
|
||||
description: 'Create a subtle transparency effect',
|
||||
sampleOptions: {
|
||||
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: ''
|
||||
}
|
||||
];
|
||||
|
||||
export default function ChangeOpacity({ title }: ToolComponentProps) {
|
||||
const [input, setInput] = useState<File | null>(null);
|
||||
const [result, setResult] = useState<File | null>(null);
|
||||
|
||||
const compute = (values: InitialValuesType, input: any) => {
|
||||
if (input) {
|
||||
changeOpacity(input, values).then(setResult);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<ToolContent
|
||||
title={title}
|
||||
input={input}
|
||||
inputComponent={
|
||||
<ToolImageInput
|
||||
value={input}
|
||||
onChange={setInput}
|
||||
accept={['image/*']}
|
||||
title={'Input image'}
|
||||
/>
|
||||
}
|
||||
resultComponent={
|
||||
<ToolFileResult title={'Changed image'} value={result} />
|
||||
}
|
||||
initialValues={initialValues}
|
||||
// exampleCards={exampleCards}
|
||||
getGroups={({ values, updateField }) => [
|
||||
{
|
||||
title: 'Opacity Settings',
|
||||
component: (
|
||||
<Box>
|
||||
<TextFieldWithDesc
|
||||
description="Set opacity between 0 (transparent) and 1 (opaque)"
|
||||
value={values.opacity}
|
||||
onOwnChange={(val) =>
|
||||
updateNumberField(val, 'opacity', updateField)
|
||||
}
|
||||
type="number"
|
||||
inputProps={{ step: 0.1, min: 0, max: 1 }}
|
||||
/>
|
||||
<SimpleRadio
|
||||
onClick={() => updateField('mode', 'solid')}
|
||||
checked={values.mode === 'solid'}
|
||||
description={'Set the same opacity level for all pixels'}
|
||||
title={'Apply Solid Opacity'}
|
||||
/>
|
||||
<SimpleRadio
|
||||
onClick={() => updateField('mode', 'gradient')}
|
||||
checked={values.mode === 'gradient'}
|
||||
description={'Change opacity in a gradient'}
|
||||
title={'Apply Gradient Opacity'}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Gradient Options',
|
||||
component: (
|
||||
<Box>
|
||||
<SimpleRadio
|
||||
onClick={() => updateField('gradientType', 'linear')}
|
||||
checked={values.gradientType === 'linear'}
|
||||
description={'Linear opacity direction'}
|
||||
title={'Linear Gradient'}
|
||||
/>
|
||||
<SimpleRadio
|
||||
onClick={() => updateField('gradientType', 'radial')}
|
||||
checked={values.gradientType === 'radial'}
|
||||
description={'Radial opacity direction'}
|
||||
title={'Radial Gradient'}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Opacity Area',
|
||||
component: (
|
||||
<Box>
|
||||
<TextFieldWithDesc
|
||||
description="Left position"
|
||||
value={values.areaLeft}
|
||||
onOwnChange={(val) =>
|
||||
updateNumberField(val, 'areaLeft', updateField)
|
||||
}
|
||||
type="number"
|
||||
/>
|
||||
<TextFieldWithDesc
|
||||
description="Top position"
|
||||
value={values.areaTop}
|
||||
onOwnChange={(val) =>
|
||||
updateNumberField(val, 'areaTop', updateField)
|
||||
}
|
||||
type="number"
|
||||
/>
|
||||
<TextFieldWithDesc
|
||||
description="Width"
|
||||
value={values.areaWidth}
|
||||
onOwnChange={(val) =>
|
||||
updateNumberField(val, 'areaWidth', updateField)
|
||||
}
|
||||
type="number"
|
||||
/>
|
||||
<TextFieldWithDesc
|
||||
description="Height"
|
||||
value={values.areaHeight}
|
||||
onOwnChange={(val) =>
|
||||
updateNumberField(val, 'areaHeight', updateField)
|
||||
}
|
||||
type="number"
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
]}
|
||||
compute={compute}
|
||||
/>
|
||||
);
|
||||
}
|
||||
13
src/pages/tools/image/generic/change-opacity/meta.ts
Normal file
13
src/pages/tools/image/generic/change-opacity/meta.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { defineTool } from '@tools/defineTool';
|
||||
import { lazy } from 'react';
|
||||
|
||||
export const tool = defineTool('image-generic', {
|
||||
name: 'Change image Opacity',
|
||||
path: 'change-opacity',
|
||||
icon: 'material-symbols:opacity',
|
||||
description:
|
||||
'Easily adjust the transparency of your images. Simply upload your image, 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 images',
|
||||
keywords: ['opacity', 'transparency', 'png', 'alpha', 'jpg', 'jpeg', 'image'],
|
||||
component: lazy(() => import('./index'))
|
||||
});
|
||||
125
src/pages/tools/image/generic/change-opacity/service.ts
Normal file
125
src/pages/tools/image/generic/change-opacity/service.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
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<File> {
|
||||
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;
|
||||
|
||||
if (options.mode === 'solid') {
|
||||
applySolidOpacity(ctx, img, options);
|
||||
} else {
|
||||
applyGradientOpacity(ctx, img, options);
|
||||
}
|
||||
|
||||
canvas.toBlob((blob) => {
|
||||
if (blob) {
|
||||
const newFile = new File([blob], file.name, { type: file.type });
|
||||
resolve(newFile);
|
||||
} else {
|
||||
reject(new Error('Failed to generate image blob'));
|
||||
}
|
||||
}, file.type);
|
||||
};
|
||||
img.onerror = () => reject(new Error('Failed to load image'));
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user