Skip to content

Commit

Permalink
Merge pull request #1 from Xutaotaotao/feature-img-filter
Browse files Browse the repository at this point in the history
Feature img filter
  • Loading branch information
Xutaotaotao authored May 24, 2024
2 parents 87bbdb9 + f97b22b commit e8fa841
Show file tree
Hide file tree
Showing 10 changed files with 8,942 additions and 23 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@
"compressorjs": "^1.2.1",
"cross-env": "^7.0.3",
"gh-pages": "^6.1.1",
"glfx": "^0.0.4",
"heic2any": "^0.0.4",
"json-diff-kit": "^1.0.29",
"lodash": "^4.17.21",
"react-draggable": "^4.4.6",
"react-i18next": "^14.1.1",
"react-json-view": "^1.21.3",
"tesseract.js": "^5.1.0",
"tui-image-editor": "^3.15.3",
"typescript": "^5.0.3"
}
}
4 changes: 3 additions & 1 deletion src/.umi/core/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ if (process.env.NODE_ENV === 'development') {
import React from 'react';

export async function getRoutes() {
const routes = {"imageCompress/singleImageCompress":{"path":"imageCompress/singleImageCompress","id":"imageCompress/singleImageCompress","parentId":"@@/global-layout"},"imageCompress/batchImageCompress":{"path":"imageCompress/batchImageCompress","id":"imageCompress/batchImageCompress","parentId":"@@/global-layout"},"imageCompress/index":{"path":"imageCompress","id":"imageCompress/index","parentId":"@@/global-layout"},"imgFormatConversion":{"path":"imgFormatConversion","id":"imgFormatConversion","parentId":"@@/global-layout"},"colorConversion":{"path":"colorConversion","id":"colorConversion","parentId":"@@/global-layout"},"jsonFormatting":{"path":"jsonFormatting","id":"jsonFormatting","parentId":"@@/global-layout"},"imageSlicing":{"path":"imageSlicing","id":"imageSlicing","parentId":"@@/global-layout"},"jsonDiff":{"path":"jsonDiff","id":"jsonDiff","parentId":"@@/global-layout"},"imgScan":{"path":"imgScan","id":"imgScan","parentId":"@@/global-layout"},"chrono":{"path":"chrono","id":"chrono","parentId":"@@/global-layout"},"index":{"path":"/","id":"index","parentId":"@@/global-layout"},"home":{"path":"home","id":"home","parentId":"@@/global-layout"},"@@/global-layout":{"id":"@@/global-layout","path":"/","isLayout":true}} as const;
const routes = {"imageCompress/singleImageCompress":{"path":"imageCompress/singleImageCompress","id":"imageCompress/singleImageCompress","parentId":"@@/global-layout"},"imageCompress/batchImageCompress":{"path":"imageCompress/batchImageCompress","id":"imageCompress/batchImageCompress","parentId":"@@/global-layout"},"imageCompress/index":{"path":"imageCompress","id":"imageCompress/index","parentId":"@@/global-layout"},"imgFormatConversion":{"path":"imgFormatConversion","id":"imgFormatConversion","parentId":"@@/global-layout"},"colorConversion":{"path":"colorConversion","id":"colorConversion","parentId":"@@/global-layout"},"imgEditor/index":{"path":"imgEditor","id":"imgEditor/index","parentId":"@@/global-layout"},"imgFilter/index":{"path":"imgFilter","id":"imgFilter/index","parentId":"@@/global-layout"},"jsonFormatting":{"path":"jsonFormatting","id":"jsonFormatting","parentId":"@@/global-layout"},"imageSlicing":{"path":"imageSlicing","id":"imageSlicing","parentId":"@@/global-layout"},"jsonDiff":{"path":"jsonDiff","id":"jsonDiff","parentId":"@@/global-layout"},"imgScan":{"path":"imgScan","id":"imgScan","parentId":"@@/global-layout"},"chrono":{"path":"chrono","id":"chrono","parentId":"@@/global-layout"},"index":{"path":"/","id":"index","parentId":"@@/global-layout"},"home":{"path":"home","id":"home","parentId":"@@/global-layout"},"@@/global-layout":{"id":"@@/global-layout","path":"/","isLayout":true}} as const;
return {
routes,
routeComponents: {
Expand All @@ -28,6 +28,8 @@ export async function getRoutes() {
'imageCompress/index': React.lazy(() => import(/* webpackChunkName: "src__pages__imageCompress__index" */'../../../src/pages/imageCompress/index.tsx')),
'imgFormatConversion': React.lazy(() => import(/* webpackChunkName: "src__pages__imgFormatConversion" */'../../../src/pages/imgFormatConversion.tsx')),
'colorConversion': React.lazy(() => import(/* webpackChunkName: "src__pages__colorConversion" */'../../../src/pages/colorConversion.tsx')),
'imgEditor/index': React.lazy(() => import(/* webpackChunkName: "src__pages__imgEditor__index" */'../../../src/pages/imgEditor/index.tsx')),
'imgFilter/index': React.lazy(() => import(/* webpackChunkName: "src__pages__imgFilter__index" */'../../../src/pages/imgFilter/index.tsx')),
'jsonFormatting': React.lazy(() => import(/* webpackChunkName: "src__pages__jsonFormatting" */'../../../src/pages/jsonFormatting.tsx')),
'imageSlicing': React.lazy(() => import(/* webpackChunkName: "src__pages__imageSlicing" */'../../../src/pages/imageSlicing.tsx')),
'jsonDiff': React.lazy(() => import(/* webpackChunkName: "src__pages__jsonDiff" */'../../../src/pages/jsonDiff.tsx')),
Expand Down
1 change: 1 addition & 0 deletions src/assets/img/imgEdit.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/img/test.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 17 additions & 10 deletions src/layouts/menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import imgScanSvg from "@/assets/img/imgScan.svg";
import imgFormatConversion from "@/assets/img/imgFormatConversion.svg";
import timeSvg from "@/assets/img/time.svg";
import colorSvg from "@/assets/img/color.svg";
import imgEdit from "@/assets/img/imgEdit.svg";

export const NAV_MAP = [
{
Expand All @@ -26,6 +27,18 @@ export const NAV_MAP = [
icon: <img src={JsonDiffSvg} />,
des:'比较两个JSON对象,并展示差异'
},
{
itemKey: "/chrono",
text: "计时器",
icon: <img src={timeSvg} />,
des:'用于监控事物的持续时间'
},
{
itemKey: "/colorConversion",
text: "颜色转换器",
icon: <img src={colorSvg} />,
des:'在不同格式之间转换颜色'
},
{
itemKey: "/imageSlicing",
text: "图片分割",
Expand All @@ -51,15 +64,9 @@ export const NAV_MAP = [
des:'图片格式互相转换'
},
{
itemKey: "/chrono",
text: "计时器",
icon: <img src={timeSvg} />,
des:'用于监控事物的持续时间'
},
{
itemKey: "/colorConversion",
text: "颜色转换器",
icon: <img src={colorSvg} />,
des:'在不同格式之间转换颜色'
itemKey: "/imgEditor",
text: "图片编辑器",
icon: <img src={imgEdit} />,
des:'图片编辑器,支持裁剪、旋转、滤镜等'
},
];
44 changes: 44 additions & 0 deletions src/pages/imgEditor/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import ImageEditor from "tui-image-editor";
import "tui-color-picker/dist/tui-color-picker.css";
import "tui-image-editor/dist/tui-image-editor.css";
import { useEffect, useRef } from "react";
import TestImg from "@/assets/img/test.jpg"

export default function ImageEditorPage() {
const editorRef = useRef<HTMLDivElement>(null);

const options = {
includeUI: {
menuBarPosition: "left",
initMenu: "filter",
loadImage:{
path:TestImg,
name:'预览'
},
locale:{
Download: "下载",
Load: "上传",
},
theme:{
"common.bi.image": "",
"downloadButton.backgroundColor": "#00a9ff",
"downloadButton.border":"#00a9ff"
}
},
selectionStyle: {
cornerSize: 20,
rotatingPointOffset: 70,
},

};

useEffect(() => {
if (!editorRef.current) return;
new ImageEditor(editorRef.current,options);
}, []);

return <div style={{ width: "100%", height: 'calc(100vh - 160px)' }}>
<div id="tui-image-editor" style={{ width: "100%", height: "100%" }} ref={editorRef}></div>
</div>

}
3 changes: 3 additions & 0 deletions src/pages/imgFilter/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.filter-item{
padding: 16px;
}
140 changes: 140 additions & 0 deletions src/pages/imgFilter/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { InboxOutlined } from "@ant-design/icons";
import { Slider } from "@douyinfe/semi-ui";
import type { UploadFile } from "antd";
import { Col, Row, Upload } from "antd";
import { useEffect, useRef, useState } from "react";
// @ts-ignore
import fx from "glfx";
import "./index.scss";

const { Dragger } = Upload;

export default function ImgFilter() {
const [fileList, setFileList] = useState<Array<any>>([]);
const [currentImg, setCurrentImg] = useState<any>(null);
const [fxData, setFxData] = useState<any>({
canvas: null,
textur: null,
});
const canvasRef = useRef<HTMLCanvasElement>(null);
const [filterValue, setFilterValue] = useState<any>({
brightness: 0,
contrast: 0,
hue: 0,
saturation: 0,
vibrance:0,
denoise:25,
});

const filterSetting = [
{ key: "brightness", label: "Brightness", min: -1, max: 1, step: 0.01 },
{ key: "contrast", label: "Contrast", min: -1, max: 1, step: 0.01 },
{ key: "hue", label: "Hue", min: -1, max: 1, step: 0.01 },
{ key: "saturation", label: "Saturation", min: -1, max: 1, step: 0.01 },
{ key: "vibrance", label: "Vibrance", min: -1, max: 1, step: 0.01 },
{ key: "denoise", label: "Denoise", min: 0, max: 50, step: 1,defaultValue:25 },

];

const filesChange = (info: any) => {
const hasFile = fileList.find(
(file: UploadFile) => info.file.name === file.name
);
if (hasFile) {
return;
} else {
setFileList([info.file]);
let reader = new FileReader();
reader.readAsDataURL(info.file.originFileObj);
const img: any = document.createElement("img");
reader.onload = function (e: any) {
img.src = e.target.result;
setCurrentImg(img);
};
img.onload = function () {
const canvas: any = canvasRef.current;
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
};
}
};

const redrawCanvas = () => {
const canvas = fx.canvas();
const texture = canvas.texture(currentImg);
setFxData({
canvas,
textur: texture,
});
};

const sliderOnChange = (key: any, value: any) => {
setFilterValue((prev: any) => ({ ...prev, [key]: value }));
};

useEffect(() => {
if (currentImg) {
redrawCanvas();
}
}, [currentImg]);

useEffect(() => {
if (fxData.canvas) {
fxData.canvas
.draw(fxData.textur)
.brightnessContrast(filterValue.brightness, filterValue.contrast)
.hueSaturation(filterValue.hue, filterValue.saturation)
.vibrance(filterValue.vibrance)
.denoise(filterValue.denoise)
.update();
const displayCanvas: any = canvasRef.current;
displayCanvas.width = currentImg.width;
displayCanvas.height = currentImg.height;
const ctx = displayCanvas.getContext("2d");
ctx.drawImage(fxData.canvas, 0, 0);
}
}, [filterValue]);

return (
<div>
<Row gutter={16}>
<Col span={8}>
{filterSetting.map((item: any) => (
<div className="filter-item" key={item.key}>
<div>{item.label}</div>
<Slider
showBoundary={true}
min={item.min}
max={item.max}
step={item.step}
defaultValue={item.defaultValue}
onChange={(val) => {sliderOnChange(item.key, val) }}
></Slider>
</div>
))}
</Col>
<Col span={16}>
{fileList.length ? (
<canvas style={{ maxWidth: "100%",maxHeight:'700px' }} ref={canvasRef} />
) : (
<Dragger
name="file"
multiple
onChange={filesChange}
fileList={fileList}
customRequest={() => {}}
showUploadList={false}
>
<p className="ant-upload-drag-icon">
<InboxOutlined />
</p>
<p className="ant-upload-text">点击或拖拽上传图片</p>
</Dragger>
)}
</Col>
</Row>
</div>
);
}
Loading

0 comments on commit e8fa841

Please sign in to comment.