mirror of
https://github.com/iib0011/omni-tools.git
synced 2025-09-19 05:59:34 +02:00
feat: change pgn opacity
This commit is contained in:
36
.idea/workspace.xml
generated
36
.idea/workspace.xml
generated
@@ -4,18 +4,10 @@
|
|||||||
<option name="autoReloadType" value="SELECTIVE" />
|
<option name="autoReloadType" value="SELECTIVE" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="chore: update meta">
|
<list default="true" id="b30e2810-c4c1-4aad-b134-794e52cc1c7d" name="Changes" comment="feat: change pgn opacity">
|
||||||
<change afterPath="$PROJECT_DIR$/src/pages/tools/image/png/change-opacity/index.tsx" afterDir="false" />
|
|
||||||
<change afterPath="$PROJECT_DIR$/src/pages/tools/image/png/change-opacity/meta.ts" afterDir="false" />
|
|
||||||
<change afterPath="$PROJECT_DIR$/src/pages/tools/image/png/change-opacity/service.ts" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/package-lock.json" beforeDir="false" afterPath="$PROJECT_DIR$/package-lock.json" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/png/change-opacity/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/png/change-opacity/index.tsx" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/package.json" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/png/change-opacity/service.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/png/change-opacity/service.ts" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/components/options/ToolOptions.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/options/ToolOptions.tsx" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/pages/tools/image/png/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/image/png/index.ts" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/pages/tools/list/index.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/list/index.ts" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/pages/tools/string/reverse/meta.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/pages/tools/string/reverse/meta.ts" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/typed/jimp.d.ts" beforeDir="false" />
|
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
@@ -338,14 +330,6 @@
|
|||||||
<workItem from="1740923024259" duration="23000" />
|
<workItem from="1740923024259" duration="23000" />
|
||||||
<workItem from="1740933006573" duration="3679000" />
|
<workItem from="1740933006573" duration="3679000" />
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00099" summary="refactor: remove validation schema">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1720914492812</created>
|
|
||||||
<option name="number" value="00099" />
|
|
||||||
<option name="presentableId" value="LOCAL-00099" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1720914492812</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00100" summary="refactor: optimize imports">
|
<task id="LOCAL-00100" summary="refactor: optimize imports">
|
||||||
<option name="closed" value="true" />
|
<option name="closed" value="true" />
|
||||||
<created>1720914702655</created>
|
<created>1720914702655</created>
|
||||||
@@ -730,7 +714,15 @@
|
|||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1741419527557</updated>
|
<updated>1741419527557</updated>
|
||||||
</task>
|
</task>
|
||||||
<option name="localTasksCounter" value="148" />
|
<task id="LOCAL-00148" summary="feat: change pgn opacity">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1741423117739</created>
|
||||||
|
<option name="number" value="00148" />
|
||||||
|
<option name="presentableId" value="LOCAL-00148" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1741423117739</updated>
|
||||||
|
</task>
|
||||||
|
<option name="localTasksCounter" value="149" />
|
||||||
<servers />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
@@ -789,7 +781,6 @@
|
|||||||
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" />
|
<option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="false" />
|
||||||
<option name="CHECK_NEW_TODO" value="false" />
|
<option name="CHECK_NEW_TODO" value="false" />
|
||||||
<option name="ADD_EXTERNAL_FILES_SILENTLY" value="true" />
|
<option name="ADD_EXTERNAL_FILES_SILENTLY" value="true" />
|
||||||
<MESSAGE value="style: background svg" />
|
|
||||||
<MESSAGE value="docs: img" />
|
<MESSAGE value="docs: img" />
|
||||||
<MESSAGE value="fix: bg" />
|
<MESSAGE value="fix: bg" />
|
||||||
<MESSAGE value="chore: handle enter press on search" />
|
<MESSAGE value="chore: handle enter press on search" />
|
||||||
@@ -814,7 +805,8 @@
|
|||||||
<MESSAGE value="feat: arithmetic sequence" />
|
<MESSAGE value="feat: arithmetic sequence" />
|
||||||
<MESSAGE value="style: tools height" />
|
<MESSAGE value="style: tools height" />
|
||||||
<MESSAGE value="chore: update meta" />
|
<MESSAGE value="chore: update meta" />
|
||||||
<option name="LAST_COMMIT_MESSAGE" value="chore: update meta" />
|
<MESSAGE value="feat: change pgn opacity" />
|
||||||
|
<option name="LAST_COMMIT_MESSAGE" value="feat: change pgn opacity" />
|
||||||
</component>
|
</component>
|
||||||
<component name="XSLT-Support.FileAssociations.UIState">
|
<component name="XSLT-Support.FileAssociations.UIState">
|
||||||
<expand />
|
<expand />
|
||||||
|
@@ -7,13 +7,29 @@ import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
|
|||||||
import { CardExampleType } from '@components/examples/ToolExamples';
|
import { CardExampleType } from '@components/examples/ToolExamples';
|
||||||
import { ToolComponentProps } from '@tools/defineTool';
|
import { ToolComponentProps } from '@tools/defineTool';
|
||||||
import { updateNumberField } from '@utils/string';
|
import { updateNumberField } from '@utils/string';
|
||||||
|
import { Box } from '@mui/material';
|
||||||
|
import SimpleRadio from '@components/options/SimpleRadio';
|
||||||
|
|
||||||
type InitialValuesType = {
|
type InitialValuesType = {
|
||||||
opacity: number;
|
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 = {
|
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<InitialValuesType>[] = [
|
const exampleCards: CardExampleType<InitialValuesType>[] = [
|
||||||
@@ -21,7 +37,14 @@ const exampleCards: CardExampleType<InitialValuesType>[] = [
|
|||||||
title: 'Semi-transparent PNG',
|
title: 'Semi-transparent PNG',
|
||||||
description: 'Make an image 50% transparent',
|
description: 'Make an image 50% transparent',
|
||||||
sampleOptions: {
|
sampleOptions: {
|
||||||
opacity: 0.5
|
opacity: 0.5,
|
||||||
|
mode: 'solid',
|
||||||
|
gradientType: 'linear',
|
||||||
|
gradientDirection: 'left-to-right',
|
||||||
|
areaLeft: 0,
|
||||||
|
areaTop: 0,
|
||||||
|
areaWidth: 100,
|
||||||
|
areaHeight: 100
|
||||||
},
|
},
|
||||||
sampleResult: ''
|
sampleResult: ''
|
||||||
},
|
},
|
||||||
@@ -29,7 +52,29 @@ const exampleCards: CardExampleType<InitialValuesType>[] = [
|
|||||||
title: 'Slightly Faded PNG',
|
title: 'Slightly Faded PNG',
|
||||||
description: 'Create a subtle transparency effect',
|
description: 'Create a subtle transparency effect',
|
||||||
sampleOptions: {
|
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: ''
|
sampleResult: ''
|
||||||
}
|
}
|
||||||
@@ -41,7 +86,7 @@ export default function ChangeOpacity({ title }: ToolComponentProps) {
|
|||||||
|
|
||||||
const compute = (values: InitialValuesType, input: any) => {
|
const compute = (values: InitialValuesType, input: any) => {
|
||||||
if (input) {
|
if (input) {
|
||||||
changeOpacity(input, values.opacity).then(setResult);
|
changeOpacity(input, values).then(setResult);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
@@ -64,20 +109,92 @@ export default function ChangeOpacity({ title }: ToolComponentProps) {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
initialValues={initialValues}
|
initialValues={initialValues}
|
||||||
exampleCards={exampleCards}
|
// exampleCards={exampleCards}
|
||||||
getGroups={({ values, updateField }) => [
|
getGroups={({ values, updateField }) => [
|
||||||
{
|
{
|
||||||
title: 'Opacity Settings',
|
title: 'Opacity Settings',
|
||||||
component: (
|
component: (
|
||||||
<TextFieldWithDesc
|
<Box>
|
||||||
description="Set opacity between 0 (transparent) and 1 (opaque)"
|
<TextFieldWithDesc
|
||||||
value={values.opacity}
|
description="Set opacity between 0 (transparent) and 1 (opaque)"
|
||||||
onOwnChange={(val) =>
|
value={values.opacity}
|
||||||
updateNumberField(val, 'opacity', updateField)
|
onOwnChange={(val) =>
|
||||||
}
|
updateNumberField(val, 'opacity', updateField)
|
||||||
type="number"
|
}
|
||||||
inputProps={{ step: 0.1, min: 0, max: 1 }}
|
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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
|
@@ -1,7 +1,15 @@
|
|||||||
export async function changeOpacity(
|
interface OpacityOptions {
|
||||||
file: File,
|
opacity: number;
|
||||||
opacity: number
|
mode: 'solid' | 'gradient';
|
||||||
): Promise<File> {
|
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) => {
|
return new Promise((resolve, reject) => {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = (event) => {
|
reader.onload = (event) => {
|
||||||
@@ -13,13 +21,14 @@ export async function changeOpacity(
|
|||||||
reject(new Error('Canvas context not supported'));
|
reject(new Error('Canvas context not supported'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas.width = img.width;
|
canvas.width = img.width;
|
||||||
canvas.height = img.height;
|
canvas.height = img.height;
|
||||||
|
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
if (options.mode === 'solid') {
|
||||||
ctx.globalAlpha = opacity;
|
applySolidOpacity(ctx, img, options);
|
||||||
ctx.drawImage(img, 0, 0);
|
} else {
|
||||||
|
applyGradientOpacity(ctx, img, options);
|
||||||
|
}
|
||||||
|
|
||||||
canvas.toBlob((blob) => {
|
canvas.toBlob((blob) => {
|
||||||
if (blob) {
|
if (blob) {
|
||||||
@@ -31,9 +40,82 @@ export async function changeOpacity(
|
|||||||
}, 'image/png');
|
}, 'image/png');
|
||||||
};
|
};
|
||||||
img.onerror = () => reject(new Error('Failed to load image'));
|
img.onerror = () => reject(new Error('Failed to load image'));
|
||||||
img.src = <string>event.target?.result;
|
img.src = event.target?.result as string;
|
||||||
};
|
};
|
||||||
reader.onerror = () => reject(new Error('Failed to read file'));
|
reader.onerror = () => reject(new Error('Failed to read file'));
|
||||||
reader.readAsDataURL(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