{acceptAction === "draw" ? <>
-
setHoverInvert(true)} onMouseLeave={() => setHoverInvert(false)}/>
-
setHoverUndo(true)} onMouseLeave={() => setHoverUndo(false)}/>
+
setHoverInvert(true)} onMouseLeave={() => setHoverInvert(false)}/>
+ {/*
setHoverClear(true)} onMouseLeave={() => setHoverClear(false)}/> */}
+
setHoverUndo(true)} onMouseLeave={() => setHoverUndo(false)}/>
+
setHoverIncreaseSize(true)} onMouseLeave={() => setHoverIncreaseSize(false)}/>
+
setHoverDecreaseSize(true)} onMouseLeave={() => setHoverDecreaseSize(false)}/>
setBrushColor(event.target.value)} value={brushColor}>
triggerAction("cancel")} onMouseEnter={() => setHoverCancel(true)} onMouseLeave={() => setHoverCancel(false)}/>
triggerAction("accept")} onMouseEnter={() => setHoverAccept(true)} onMouseLeave={() => setHoverAccept(false)}/>
diff --git a/main.ts b/main.ts
index a86b9c5..4bfeac8 100644
--- a/main.ts
+++ b/main.ts
@@ -16,6 +16,7 @@ let window: Electron.BrowserWindow | null
let currentDialog: Electron.BrowserWindow | null
autoUpdater.autoDownload = false
const store = new Store()
+let tempStore = {} as any
let filePath = ""
let originalImages = null as any
@@ -49,6 +50,18 @@ const saveImage = async (image: any, savePath: string) => {
}
}
+ipcMain.handle("clear-temp", (event, key: string) => {
+ tempStore = {}
+})
+
+ipcMain.handle("get-temp", (event, key: string) => {
+ return tempStore[key]
+})
+
+ipcMain.handle("save-temp", (event, key: string, value: string) => {
+ tempStore[key] = value
+})
+
const getDimensions = async (image: any) => {
const metadata = await sharp(image).metadata()
return {width: metadata.width ?? 0, height: metadata.height ?? 0}
@@ -129,7 +142,7 @@ ipcMain.handle("show-bulk-save-dialog", async (event) => {
if (currentDialog.type === "bulk-save") return
}
const bounds = window?.getBounds()!
- currentDialog = new BrowserWindow({width: 230, height: 170, x: Math.floor(bounds.width/2) + 200, y: Math.floor(bounds.height/2), resizable: false, show: false, frame: false, backgroundColor: "#3177f5", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}})
+ currentDialog = new BrowserWindow({width: 230, height: 170, x: Math.floor(bounds.width/2) + 200, y: Math.floor(bounds.height/2), resizable: false, show: false, frame: false, transparent: true, hasShadow: false, backgroundColor: "#00000000", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}})
currentDialog.loadFile(path.join(__dirname, "bulksavedialog.html"))
currentDialog.removeMenu()
currentDialog.setAlwaysOnTop(true)
@@ -165,6 +178,18 @@ ipcMain.handle("draw-invert", () => {
window?.webContents.send("draw-invert")
})
+ipcMain.handle("draw-clear", () => {
+ window?.webContents.send("draw-clear")
+})
+
+ipcMain.handle("draw-increase-size", () => {
+ window?.webContents.send("draw-increase-size")
+})
+
+ipcMain.handle("draw-decrease-size", () => {
+ window?.webContents.send("draw-decrease-size")
+})
+
ipcMain.handle("get-info", (event: any, image: string) => {
window?.webContents.send("close-all-dialogs", "info")
window?.webContents.send("show-info-dialog", image)
@@ -221,7 +246,7 @@ ipcMain.handle("show-gif-dialog", async (event) => {
if (currentDialog.type === "gif") return
}
const bounds = window?.getBounds()!
- currentDialog = new BrowserWindow({width: 190, height: 175, x: bounds.x + bounds.width - 190 - 170, y: bounds.y + 60, resizable: false, show: false, transparent: true, frame: false, backgroundColor: "#3177f5", roundedCorners: false, webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}})
+ currentDialog = new BrowserWindow({width: 190, height: 175, x: bounds.x + bounds.width - 190 - 170, y: bounds.y + 60, resizable: false, show: false, frame: false, transparent: true, hasShadow: false, backgroundColor: "#00000000", roundedCorners: false, webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}})
currentDialog.loadFile(path.join(__dirname, "gifdialog.html"))
currentDialog.removeMenu()
currentDialog.setAlwaysOnTop(true)
@@ -395,7 +420,7 @@ ipcMain.handle("show-crop-dialog", async (event) => {
if (currentDialog.type === "crop") return
}
const bounds = window?.getBounds()!
- currentDialog = new BrowserWindow({width: 190, height: 220, x: bounds.x + 70, y: bounds.y + 400, resizable: false, show: false, frame: false, backgroundColor: "#3177f5", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}})
+ currentDialog = new BrowserWindow({width: 190, height: 220, x: bounds.x + 70, y: bounds.y + 400, resizable: false, show: false, frame: false, transparent: true, hasShadow: false, backgroundColor: "#00000000", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}})
currentDialog.loadFile(path.join(__dirname, "cropdialog.html"))
currentDialog.removeMenu()
currentDialog.setAlwaysOnTop(true)
@@ -466,7 +491,7 @@ ipcMain.handle("show-rotate-dialog", async (event) => {
if (currentDialog.type === "rotate") return
}
const bounds = window?.getBounds()!
- currentDialog = new BrowserWindow({width: 230, height: 170, x: bounds.x + bounds.width - 230 - 70, y: bounds.y + 60, resizable: false, show: false, frame: false, backgroundColor: "#3177f5", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}})
+ currentDialog = new BrowserWindow({width: 230, height: 170, x: bounds.x + bounds.width - 230 - 70, y: bounds.y + 60, resizable: false, show: false, frame: false, transparent: true, hasShadow: false, backgroundColor: "#00000000", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}})
currentDialog.loadFile(path.join(__dirname, "rotatedialog.html"))
currentDialog.removeMenu()
currentDialog.setAlwaysOnTop(true)
@@ -538,7 +563,7 @@ ipcMain.handle("show-resize-dialog", async (event) => {
if (currentDialog.type === "resize") return
}
const bounds = window?.getBounds()!
- currentDialog = new BrowserWindow({width: 230, height: 150, x: bounds.x + bounds.width - 230 - 70, y: bounds.y + 40, resizable: false, show: false, frame: false, backgroundColor: "#3177f5", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}})
+ currentDialog = new BrowserWindow({width: 230, height: 150, x: bounds.x + bounds.width - 230 - 70, y: bounds.y + 40, resizable: false, show: false, frame: false, transparent: true, hasShadow: false, backgroundColor: "#00000000", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}})
currentDialog.loadFile(path.join(__dirname, "resizedialog.html"))
currentDialog.removeMenu()
currentDialog.setAlwaysOnTop(true)
@@ -607,7 +632,7 @@ ipcMain.handle("show-binarize-dialog", async (event) => {
if (currentDialog.type === "binarize") return
}
const bounds = window?.getBounds()!
- currentDialog = new BrowserWindow({width: 250, height: 130, x: bounds.x + 70, y: bounds.y + 450, resizable: false, show: false, frame: false, backgroundColor: "#3177f5", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}})
+ currentDialog = new BrowserWindow({width: 250, height: 130, x: bounds.x + 70, y: bounds.y + 450, resizable: false, show: false, frame: false, transparent: true, hasShadow: false, backgroundColor: "#00000000", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}})
currentDialog.loadFile(path.join(__dirname, "binarizedialog.html"))
currentDialog.removeMenu()
currentDialog.setAlwaysOnTop(true)
@@ -684,7 +709,7 @@ ipcMain.handle("show-pixelate-dialog", async (event) => {
if (currentDialog.type === "pixelate") return
}
const bounds = window?.getBounds()!
- currentDialog = new BrowserWindow({width: 250, height: 130, x: bounds.x + 70, y: bounds.y + 330, resizable: false, show: false, frame: false, backgroundColor: "#3177f5", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}})
+ currentDialog = new BrowserWindow({width: 250, height: 130, x: bounds.x + 70, y: bounds.y + 330, resizable: false, show: false, frame: false, transparent: true, hasShadow: false, backgroundColor: "#00000000", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}})
currentDialog.loadFile(path.join(__dirname, "pixelatedialog.html"))
currentDialog.removeMenu()
currentDialog.setAlwaysOnTop(true)
@@ -755,7 +780,7 @@ ipcMain.handle("show-blur-dialog", async (event) => {
if (currentDialog.type === "blur") return
}
const bounds = window?.getBounds()!
- currentDialog = new BrowserWindow({width: 250, height: 155, x: bounds.x + 70, y: bounds.y + 190, resizable: false, show: false, frame: false, backgroundColor: "#3177f5", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}})
+ currentDialog = new BrowserWindow({width: 250, height: 155, x: bounds.x + 70, y: bounds.y + 190, resizable: false, show: false, frame: false, transparent: true, hasShadow: false, backgroundColor: "#00000000", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}})
currentDialog.loadFile(path.join(__dirname, "blurdialog.html"))
currentDialog.removeMenu()
currentDialog.setAlwaysOnTop(true)
@@ -824,7 +849,7 @@ ipcMain.handle("show-tint-dialog", async (event) => {
if (currentDialog.type === "tint") return
}
const bounds = window?.getBounds()!
- currentDialog = new BrowserWindow({width: 180, height: 135, x: bounds.x + 70, y: bounds.y + 130, resizable: false, show: false, frame: false, backgroundColor: "#3177f5", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}})
+ currentDialog = new BrowserWindow({width: 180, height: 135, x: bounds.x + 70, y: bounds.y + 130, resizable: false, show: false, frame: false, transparent: true, hasShadow: false, backgroundColor: "#00000000", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}})
currentDialog.loadFile(path.join(__dirname, "tintdialog.html"))
currentDialog.removeMenu()
currentDialog.setAlwaysOnTop(true)
@@ -893,7 +918,7 @@ ipcMain.handle("show-hsl-dialog", async (event) => {
if (currentDialog.type === "hsl") return
}
const bounds = window?.getBounds()!
- currentDialog = new BrowserWindow({width: 250, height: 180, x: bounds.x + 70, y: bounds.y + 50, resizable: false, show: false, frame: false, backgroundColor: "#3177f5", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}})
+ currentDialog = new BrowserWindow({width: 250, height: 180, x: bounds.x + 70, y: bounds.y + 50, resizable: false, show: false, frame: false, transparent: true, hasShadow: false, backgroundColor: "#00000000", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}})
currentDialog.loadFile(path.join(__dirname, "hsldialog.html"))
currentDialog.removeMenu()
currentDialog.setAlwaysOnTop(true)
@@ -963,9 +988,8 @@ ipcMain.handle("show-brightness-dialog", async (event) => {
// @ts-expect-error
if (currentDialog.type === "brightness") return
}
- const backgroundColor = "#3177f5"
const bounds = window?.getBounds()!
- currentDialog = new BrowserWindow({width: 250, height: 150, x: bounds.x + 70, y: bounds.y + 40, resizable: false, show: false, frame: false, backgroundColor, webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}})
+ currentDialog = new BrowserWindow({width: 250, height: 150, x: bounds.x + 70, y: bounds.y + 40, resizable: false, show: false, frame: false, transparent: true, hasShadow: false, backgroundColor: "#00000000", webPreferences: {nodeIntegration: true, contextIsolation: false, webSecurity: false}})
currentDialog.loadFile(path.join(__dirname, "brightnessdialog.html"))
currentDialog.removeMenu()
currentDialog.setAlwaysOnTop(true)
@@ -1304,7 +1328,7 @@ if (!singleLock) {
app.on("ready", () => {
app.commandLine.appendSwitch('disable-features', 'DarkMode');
- window = new BrowserWindow({width: 900, height: 650, minWidth: 520, minHeight: 250, show: false, transparent: true, frame: false, hasShadow: false, backgroundColor: "#00000000", center: true, roundedCorners: false, webPreferences: {nodeIntegration: true, contextIsolation: false, enableRemoteModule: true, webSecurity: false}})
+ window = new BrowserWindow({width: 900, height: 650, minWidth: 520, minHeight: 250, show: false,frame: false, transparent: true, hasShadow: false, backgroundColor: "#00000000", center: true, roundedCorners: false, webPreferences: {nodeIntegration: true, contextIsolation: false, enableRemoteModule: true, webSecurity: false}})
window.loadFile(path.join(__dirname, "index.html"))
window.removeMenu()
openFile()
diff --git a/package-lock.json b/package-lock.json
index 1e65957..acdf2ab 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -28,7 +28,7 @@
"react-draggable": "^4.4.3",
"react-dropzone": "^11.3.4",
"react-image-crop": "^9.0.4",
- "react-zoom-pan-pinch": "^2.1.3",
+ "react-zoom-pan-pinch": "^3.6.1",
"sharp": "^0.33.5",
"unzipper": "^0.10.11"
},
@@ -7583,16 +7583,16 @@
}
},
"node_modules/react-zoom-pan-pinch": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/react-zoom-pan-pinch/-/react-zoom-pan-pinch-2.1.3.tgz",
- "integrity": "sha512-a5AChOWhjo0RmxsNZXGQIlNh3e3nLU6m4V6M+6dlbPNk5d+MtMxgKWyA5zpR06Lp3OZkZVF9nR8JeWSvKwck9g==",
+ "version": "3.6.1",
+ "resolved": "https://registry.npmjs.org/react-zoom-pan-pinch/-/react-zoom-pan-pinch-3.6.1.tgz",
+ "integrity": "sha512-SdPqdk7QDSV7u/WulkFOi+cnza8rEZ0XX4ZpeH7vx3UZEg7DoyuAy3MCmm+BWv/idPQL2Oe73VoC0EhfCN+sZQ==",
"engines": {
"node": ">=8",
"npm": ">=5"
},
"peerDependencies": {
- "react": "^17.0.2",
- "react-dom": "^17.0.2"
+ "react": "*",
+ "react-dom": "*"
}
},
"node_modules/read-config-file": {
@@ -15503,9 +15503,9 @@
}
},
"react-zoom-pan-pinch": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/react-zoom-pan-pinch/-/react-zoom-pan-pinch-2.1.3.tgz",
- "integrity": "sha512-a5AChOWhjo0RmxsNZXGQIlNh3e3nLU6m4V6M+6dlbPNk5d+MtMxgKWyA5zpR06Lp3OZkZVF9nR8JeWSvKwck9g==",
+ "version": "3.6.1",
+ "resolved": "https://registry.npmjs.org/react-zoom-pan-pinch/-/react-zoom-pan-pinch-3.6.1.tgz",
+ "integrity": "sha512-SdPqdk7QDSV7u/WulkFOi+cnza8rEZ0XX4ZpeH7vx3UZEg7DoyuAy3MCmm+BWv/idPQL2Oe73VoC0EhfCN+sZQ==",
"requires": {}
},
"read-config-file": {
diff --git a/package.json b/package.json
index 609681e..009fc69 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "photo-viewer",
- "version": "0.2.4",
+ "version": "0.2.5",
"description": "An image/GIF viewer that can apply various resizing and color effects.",
"main": "dist/main.js",
"scripts": {
@@ -169,7 +169,7 @@
"react-draggable": "^4.4.3",
"react-dropzone": "^11.3.4",
"react-image-crop": "^9.0.4",
- "react-zoom-pan-pinch": "^2.1.3",
+ "react-zoom-pan-pinch": "^3.6.1",
"sharp": "^0.33.5",
"unzipper": "^0.10.11"
}
diff --git a/structures/CanvasDraw.js b/structures/CanvasDraw.js
index d7c6ae3..2af5040 100644
--- a/structures/CanvasDraw.js
+++ b/structures/CanvasDraw.js
@@ -111,6 +111,8 @@ export default class extends PureComponent {
hideInterface: PropTypes.bool,
erase: PropTypes.bool,
eraseColor: PropTypes.string,
+ zoom: PropTypes.number,
+ spin: PropTypes.number
};
static defaultProps = {
@@ -131,7 +133,9 @@ export default class extends PureComponent {
immediateLoading: false,
hideInterface: false,
erase: false,
- eraseColor: "#000000"
+ eraseColor: "#00000000",
+ zoom: 1,
+ spin: 0
};
constructor(props) {
@@ -211,6 +215,10 @@ export default class extends PureComponent {
if (prevProps.imgSrc !== this.props.imgSrc) {
this.drawImage();
}
+
+ if (prevProps.zoom !== this.props.zoom) {
+ this.loop();
+ }
}
componentWillUnmount = () => {
@@ -393,6 +401,10 @@ export default class extends PureComponent {
handleDrawStart = e => {
e.preventDefault();
+ if (e.button === 1 || e.button === 2) {
+ return;
+ }
+
// Start drawing
this.isPressing = true;
@@ -522,21 +534,26 @@ export default class extends PureComponent {
let p1 = points[0];
let p2 = points[1];
- this.ctx.temp.moveTo(p2.x, p2.y);
+ let newP1 = this.modifyCoordinates(p1);
+ let newP2 = this.modifyCoordinates(p2);
+
+ this.ctx.temp.moveTo(newP2.x, newP2.y);
this.ctx.temp.beginPath();
for (var i = 1, len = points.length; i < len; i++) {
// we pick the point between pi+1 & pi+2 as the
// end point and p1 as our control point
var midPoint = midPointBtw(p1, p2);
- this.ctx.temp.quadraticCurveTo(p1.x, p1.y, midPoint.x, midPoint.y);
+ newP1 = this.modifyCoordinates(p1);
+ midPoint = this.modifyCoordinates(midPoint);
+ this.ctx.temp.quadraticCurveTo(newP1.x, newP1.y, midPoint.x, midPoint.y);
p1 = points[i];
p2 = points[i + 1];
}
// Draw last line as a straight line while
// we wait for the next point to be able to calculate
// the bezier control point
- this.ctx.temp.lineTo(p1.x, p1.y);
+ this.ctx.temp.lineTo(newP1.x, newP1.y);
this.ctx.temp.stroke();
};
@@ -637,6 +654,13 @@ export default class extends PureComponent {
ctx.stroke();
};
+ modifyCoordinates = (point) => {
+ if (!point) return;
+ let x = point.x / this.props.zoom;
+ let y = point.y / this.props.zoom;
+ return {x, y}
+ }
+
drawInterface = (ctx, pointer, brush) => {
if (this.props.hideInterface) return;
@@ -644,17 +668,21 @@ export default class extends PureComponent {
// Color brush preview according to erase prop
const brushColor = this.props.erase ? this.props.eraseColor : this.props.brushColor;
-
+ let brushX = brush.x / this.props.zoom;
+ let brushY = brush.y / this.props.zoom;
+ let pointerX = pointer.x / this.props.zoom;
+ let pointerY = pointer.y / this.props.zoom;
+
// Draw brush preview
ctx.beginPath();
ctx.fillStyle = brushColor;
- ctx.arc(brush.x, brush.y, this.props.brushRadius, 0, Math.PI * 2, true);
+ ctx.arc(brushX, brushY, this.props.brushRadius, 0, Math.PI * 2, true);
ctx.fill();
// Draw mouse point (the one directly at the cursor)
ctx.beginPath();
ctx.fillStyle = this.props.catenaryColor;
- ctx.arc(pointer.x, pointer.y, 4, 0, Math.PI * 2, true);
+ ctx.arc(pointerX, pointerY, 4, 0, Math.PI * 2, true);
ctx.fill();
// Draw catenary
@@ -676,7 +704,7 @@ export default class extends PureComponent {
// Draw brush point (the one in the middle of the brush preview)
ctx.beginPath();
ctx.fillStyle = this.props.catenaryColor;
- ctx.arc(brush.x, brush.y, 2, 0, Math.PI * 2, true);
+ ctx.arc(brushX, brushY, 2, 0, Math.PI * 2, true);
ctx.fill();
};