Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,987 changes: 1,987 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
"license": "MIT",
"dependencies": {
"electron": "^28.0.0",
"python-shell": "^5.0.1"
"python-shell": "5.0.0"
},
"devDependencies": {
"eslint": "^8.55.0",
"prettier": "^3.0.0"
}
}
}
184 changes: 85 additions & 99 deletions src/index.html
Original file line number Diff line number Diff line change
@@ -1,104 +1,90 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PropFix – AI‑Assisted Photo Editor</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f5f5f5;
}
header {
background-color: #243a73;
color: white;
padding: 1rem;
text-align: center;
}
#controls {
display: flex;
justify-content: center;
gap: 1rem;
margin-top: 1rem;
}
button {
padding: 0.5rem 1rem;
border: none;
background-color: #4c8ef5;
color: white;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #3b6dc2;
}
#image-container {
display: flex;
justify-content: center;
margin-top: 2rem;
}
#image-container img {
max-width: 90%;
max-height: 60vh;
border: 1px solid #ccc;
}
</style>
</head>
<body>
<header>
<h1>PropFix</h1>
<p>AI‑assisted photo editing</p>
</header>
<div id="controls">
<button id="openBtn">Open Image</button>
<button id="enhanceBtn">Auto Enhance</button>
<button id="denoiseBtn">Denoise</button>
<button id="styleBtn">Style Transfer</button>
</div>
<div id="image-container">
<img id="preview" alt="Selected image will appear here" />
</div>
<script>
const openBtn = document.getElementById('openBtn');
const enhanceBtn = document.getElementById('enhanceBtn');
const denoiseBtn = document.getElementById('denoiseBtn');
const styleBtn = document.getElementById('styleBtn');
const preview = document.getElementById('preview');
<head>
<meta charset="utf-8" />
<title>PropFix Photo Editor</title>
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src 'self' data: file:; style-src 'self' 'unsafe-inline'; script-src 'self'; connect-src http://127.0.0.1:5001;">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="./styles.css" />
</head>
<body>
<div class="app">
<aside class="left">
<header class="left-header">
<h1>Controls</h1>
<div id="segmentsBanner" class="muted">No segments yet.</div>
</header>

let currentImagePath = null;
<section class="panel">
<div class="row compact">
<label for="searchSeg">Find segment</label>
<input id="searchSeg" type="text" placeholder="Search (e.g., arm, torso)" />
</div>
<div class="row compact">
<label for="strength">Effect amount</label>
<input id="strength" type="range" min="0" max="1" step="0.05" value="1" />
<span id="strengthVal">100%</span>
</div>
</section>

openBtn.addEventListener('click', async () => {
const files = await window.electron.openFiles();
if (files && files.length > 0) {
currentImagePath = files[0];
preview.src = `file://${currentImagePath}`;
}
});
<section class="panel">
<details open>
<summary>Body</summary>
<div id="group-body" class="group"></div>
</details>
<details>
<summary>Arms</summary>
<div id="group-arms" class="group"></div>
</details>
<details>
<summary>Legs</summary>
<div id="group-legs" class="group"></div>
</details>
<details>
<summary>Hands & Feet</summary>
<div id="group-extremities" class="group"></div>
</details>
<details>
<summary>Other</summary>
<div id="group-other" class="group"></div>
</details>
</section>

async function runCommand(cmd) {
if (!currentImagePath) {
alert('Please open an image first.');
return;
}
const result = await window.electron.processImage({
command: cmd,
image_path: currentImagePath,
params: {}
});
// Expect result to be a file path or base64 string
if (result) {
preview.src = result.startsWith('data:')
? result
: `file://${result}`;
}
}
<section class="panel footer-buttons">
<button id="resetBtn" class="btn ghost">Reset All</button>
<button id="logsBtn" class="btn ghost">Logs</button>
</section>

enhanceBtn.addEventListener('click', () => runCommand('enhance'));
denoiseBtn.addEventListener('click', () => runCommand('denoise'));
styleBtn.addEventListener('click', () => runCommand('style'));
</script>
</body>
</html>
<section id="logsPane" class="panel hidden">
<textarea id="logArea" readonly></textarea>
</section>
</aside>

<main class="right">
<header class="topbar">
<input id="inputPath" type="text" placeholder="Full path to input image (e.g., C:\path\to\alpha.png)" />
<div class="topbar-actions">
<button id="annotateBtn" class="btn primary">Annotate</button>
<div class="seg-count" id="segCount">–</div>
<div class="divider"></div>
<div class="zoom-controls">
<button class="btn ghost" id="fitBtn" title="Fit to window">Fit</button>
<button class="btn ghost" id="fillBtn" title="Fill width">Fill</button>
<button class="btn ghost" id="oneBtn" title="1:1">1:1</button>
</div>
</div>
</header>

<section class="canvas">
<div id="viewport" class="viewport fit">
<img id="previewImg" alt="preview" />
</div>
</section>

<footer id="statusBar" class="status">Idle.</footer>
</main>
</div>

<script src="./renderer.js"></script>
</body>
</html>
42 changes: 42 additions & 0 deletions src/main.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
const { app, BrowserWindow, dialog, ipcMain, Menu } = require('electron');
const path = require('path');

function createMenu(win) {
const template = [{
label: 'View',
submenu: [
{ role: 'reload' }, { role: 'forceReload' }, { type: 'separator' },
{ label: 'Toggle Developer Tools',
accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
click: () => win.webContents.toggleDevTools() }
]
}];
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
}

function createWindow() {
const win = new BrowserWindow({
width: 1200, height: 800,
webPreferences: { contextIsolation: true, nodeIntegration: false, preload: path.join(__dirname, 'preload.cjs'), sandbox: true, devTools: true },
title: 'PropFix Photo Editor'
});
createMenu(win);
win.loadFile(path.join(__dirname, 'index.html'));
win.webContents.openDevTools({ mode: 'detach' });
}

app.whenReady().then(() => {
ipcMain.handle('chooseExportPath', async (_evt, suggested) => {
const { canceled, filePath } = await dialog.showSaveDialog({
title: 'Export warped image',
defaultPath: suggested || 'output.png',
filters: [{ name: 'PNG Image', extensions: ['png'] }]
});
return canceled ? null : filePath;
});
createWindow();
app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) createWindow(); });
});
app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit(); });


10 changes: 5 additions & 5 deletions src/main.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
const { app, BrowserWindow, ipcMain, dialog } = require('electron');
const { app, BrowserWindow, ipcMain, dialog } = require('electron');
const path = require('path');
const { PythonShell } = require('python-shell');

/**
* Creates the main application window. The window loads the renderer
* (index.html) and sets up IPC handlers for communication with the
* Python back‑end. Electron’s `contextIsolation` and `preload` options
* Python back‑end. Electron’s `contextIsolation` and `preload` options
* are enabled to follow best practices for security.
*/
function createWindow() {
Expand Down Expand Up @@ -59,12 +59,12 @@ ipcMain.handle('dialog:openFile', async () => {

/**
* IPC handler: perform an AI operation by sending a command to the
* Python back‑end. The handler spawns a Python process using
* Python back‑end. The handler spawns a Python process using
* `python-shell` and returns the result to the renderer.
*/
ipcMain.handle('ai:process', async (_event, command) => {
return new Promise((resolve, reject) => {
const pyshell = new PythonShell('python/image_processing.py');
const pyshell = new PythonShell(require('path').join(__dirname, '..', 'python', 'image_processing.py'));
let resultData = '';
pyshell.on('message', (message) => {
resultData += message;
Expand All @@ -78,4 +78,4 @@ ipcMain.handle('ai:process', async (_event, command) => {
}
});
});
});
});
Loading