Skip to content

Add pencil sketch image transformation tool#6

Open
ASP-31 wants to merge 11 commits into
azmil666:mainfrom
ASP-31:main
Open

Add pencil sketch image transformation tool#6
ASP-31 wants to merge 11 commits into
azmil666:mainfrom
ASP-31:main

Conversation

@ASP-31

@ASP-31 ASP-31 commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features
    • Added a "Pencil Sketch" tool: upload an image, see a preview, apply a pencil-sketch effect, then download the result.
    • Processing now happens server-side for faster, consistent sketch rendering and includes progress/loading feedback and user-friendly error responses.

@vercel

vercel Bot commented Mar 10, 2026

Copy link
Copy Markdown

@ASP-31 is attempting to deploy a commit to the Azmil's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Mar 10, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds a new "Pencil Sketch" feature: a backend Express POST /api/images/sketch route that accepts multipart uploads and returns a sketchified PNG via Sharp, and a frontend tool page for upload, preview, processing, and download. The router is mounted at /api/images (registered after server listen).

Changes

Cohort / File(s) Summary
Backend Sketch Route
backend/src/routes/imageSketch.ts
New Express router exporting POST /sketch using multer (memory), validates file, runs a Sharp pipeline (grayscale → blur → invert → color-dodge blend → normalize → sharpen → PNG) and returns image/png or errors.
Backend Route Registration
backend/src/server.ts
Imports and mounts the new imageSketch router at /api/images (mounted after app.listen).
Frontend Tools Navigation
frontend/app/tools/page.tsx
Adds "Pencil Sketch" tool item linking to /tools/sketch into the tools grid.
Frontend Sketch UI
frontend/app/tools/sketch/page.tsx
New React component SketchTool handling file selection, preview, POST to /api/images/sketch, loading state, blob response handling, result display, and download button.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant Client as Browser/React
    participant Backend as Express Route
    participant Sharp as Sharp Library

    User->>Client: Select & upload image
    Client->>Client: Show local preview
    User->>Client: Click "Apply Sketch"
    Client->>Backend: POST /api/images/sketch (multipart/form-data)
    activate Backend
    Backend->>Backend: multer in-memory validation
    Backend->>Sharp: Run pipeline (grayscale, blur, invert, blend, normalize, sharpen)
    Sharp-->>Backend: PNG buffer
    Backend->>Client: 200 OK (image/png blob)
    deactivate Backend
    Client->>Client: Display result and enable "Download"
    User->>Client: Click "Download"
    Client->>User: Save pencil-sketch.png
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Add filters v1 #3 — Adds an image-processing route and also mounts an /api/images router in backend/src/server.ts, overlapping routing changes.

Poem

🐇 With whiskers twitching, I nibble code and light,
I blur, invert, and dodge to sketch from bright to slight,
A file uploads, I dance through Sharp's fine art,
Out pops a PNG — a pencil-hearted start.
Hop, click, download — the rabbit’s sketched delight.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add pencil sketch image transformation tool' accurately summarizes the main change: a new pencil sketch feature that transforms images, as evidenced by new routes, UI components, and backend processing logic.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
frontend/app/tools/sketch/page.tsx (1)

15-17: Revoke stale object URLs.

Both preview and result URLs are leaked right now. After a few large images, this page will keep extra blob data alive until refresh.

Suggested fix
 "use client";
 
-import { useState } from "react";
+import { useEffect, useState } from "react";
@@
   const [preview, setPreview] = useState<string | null>(null);
   const [result, setResult] = useState<string | null>(null);
   const [loading, setLoading] = useState(false);
+
+  useEffect(() => {
+    return () => {
+      if (preview) URL.revokeObjectURL(preview);
+      if (result) URL.revokeObjectURL(result);
+    };
+  }, [preview, result]);
@@
-    setPreview(URL.createObjectURL(uploaded));
+    setPreview((prev) => {
+      if (prev) URL.revokeObjectURL(prev);
+      return URL.createObjectURL(uploaded);
+    });
     setResult(null);

Also applies to: 33-36

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/tools/sketch/page.tsx` around lines 15 - 17, The preview/result
object URLs are leaking because URL.createObjectURL(uploaded) is called without
revoking previous blobs; update the logic around setFile/setPreview/setResult so
you revoke any existing preview and result URLs before replacing them (use
URL.revokeObjectURL(previousPreview) and URL.revokeObjectURL(previousResult>)
and also revoke in the component cleanup (useEffect return) to ensure no stale
blob URLs remain; locate the code paths that call
setPreview(URL.createObjectURL(...)) and the similar URLs at the later block
(the lines that setPreview/setResult around the upload handling) and add
revocation before assigning and on unmount.
backend/src/server.ts (1)

3-8: Register this router in backend/src/app.ts instead.

Right now /api/images/sketch only exists when the process boots through backend/src/server.ts; anything importing app directly gets a different app shape. Keeping route mounts in one place avoids that split.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/src/server.ts` around lines 3 - 8, The router mount for imageSketch
is currently in server boot code (app.use("/api/images", imageSketch) next to
app.listen) which causes different app shapes for consumers; remove that mount
from the server file and instead import and register the router inside the
module that constructs and exports the Express app (where the Express "app" is
created, e.g., in app.ts), by adding import imageSketch and
app.use("/api/images", imageSketch) there before exporting the app so any module
importing the app sees the same routes; leave app.listen only in server code
that imports the already-configured app.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/src/routes/imageSketch.ts`:
- Around line 6-8: The route creates a local Multer instance (upload = multer({
storage: multer.memoryStorage() })) which omits the 5MB cap and can exhaust
process memory; update the POST /sketch handler to use the shared upload
middleware from upload.middleware.ts or recreate the Multer instance with
limits.fileSize set (e.g., 5 * 1024 * 1024) and keep upload.single("image") in
router.post("/sketch", ...), so memory-backed uploads are capped before Sharp
runs.

In `@frontend/app/tools/sketch/page.tsx`:
- Around line 20-37: applySketch currently POSTs to a hardcoded
"http://localhost:5000/api/images/sketch" and treats any response as an image
blob; update applySketch to use a configurable API base (e.g., read from an
env/client config) instead of the hardcoded localhost, immediately check res.ok
after fetch and throw or handle non-OK responses so errors don't return blobs,
ensure setLoading(false) is called in all exit paths (on success, non-OK
responses, and network exceptions), and surface or log the error before
returning so callers can react; key symbols to change: applySketch, fetch call,
res.ok check, setLoading, setResult.

---

Nitpick comments:
In `@backend/src/server.ts`:
- Around line 3-8: The router mount for imageSketch is currently in server boot
code (app.use("/api/images", imageSketch) next to app.listen) which causes
different app shapes for consumers; remove that mount from the server file and
instead import and register the router inside the module that constructs and
exports the Express app (where the Express "app" is created, e.g., in app.ts),
by adding import imageSketch and app.use("/api/images", imageSketch) there
before exporting the app so any module importing the app sees the same routes;
leave app.listen only in server code that imports the already-configured app.

In `@frontend/app/tools/sketch/page.tsx`:
- Around line 15-17: The preview/result object URLs are leaking because
URL.createObjectURL(uploaded) is called without revoking previous blobs; update
the logic around setFile/setPreview/setResult so you revoke any existing preview
and result URLs before replacing them (use URL.revokeObjectURL(previousPreview)
and URL.revokeObjectURL(previousResult>) and also revoke in the component
cleanup (useEffect return) to ensure no stale blob URLs remain; locate the code
paths that call setPreview(URL.createObjectURL(...)) and the similar URLs at the
later block (the lines that setPreview/setResult around the upload handling) and
add revocation before assigning and on unmount.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d98830c0-943f-44e6-80ad-81842a31e50d

📥 Commits

Reviewing files that changed from the base of the PR and between 4e542ed and 8df4026.

⛔ Files ignored due to path filters (3)
  • backend/package-lock.json is excluded by !**/package-lock.json
  • frontend/package-lock.json is excluded by !**/package-lock.json
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (4)
  • backend/src/routes/imageSketch.ts
  • backend/src/server.ts
  • frontend/app/tools/page.tsx
  • frontend/app/tools/sketch/page.tsx

Comment on lines +6 to +8
const upload = multer({ storage: multer.memoryStorage() });

router.post("/sketch", upload.single("image"), async (req, res) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Restore the upload size limit on this memory-backed route.

This local Multer instance drops the 5 MB cap used elsewhere, so one oversized upload can blow up process memory before Sharp ever runs. Reuse the shared middleware from backend/src/middleware/upload.middleware.ts or add an explicit limits.fileSize here.

Suggested fix
 import express from "express";
-import multer from "multer";
 import sharp from "sharp";
+import { upload } from "../middleware/upload.middleware";
 
 const router = express.Router();
-const upload = multer({ storage: multer.memoryStorage() });
 
 router.post("/sketch", upload.single("image"), async (req, res) => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const upload = multer({ storage: multer.memoryStorage() });
router.post("/sketch", upload.single("image"), async (req, res) => {
import express from "express";
import sharp from "sharp";
import { upload } from "../middleware/upload.middleware";
const router = express.Router();
router.post("/sketch", upload.single("image"), async (req, res) => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/src/routes/imageSketch.ts` around lines 6 - 8, The route creates a
local Multer instance (upload = multer({ storage: multer.memoryStorage() }))
which omits the 5MB cap and can exhaust process memory; update the POST /sketch
handler to use the shared upload middleware from upload.middleware.ts or
recreate the Multer instance with limits.fileSize set (e.g., 5 * 1024 * 1024)
and keep upload.single("image") in router.post("/sketch", ...), so memory-backed
uploads are capped before Sharp runs.

Comment on lines +20 to +37
const applySketch = async () => {
if (!file) return;

setLoading(true);

const formData = new FormData();
formData.append("image", file);

const res = await fetch("http://localhost:5000/api/images/sketch", {
method: "POST",
body: formData
});

const blob = await res.blob();
const url = URL.createObjectURL(blob);

setResult(url);
setLoading(false);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Make the API target configurable and fail fast on non-image responses.

This fetch is hardcoded to the browser's localhost, so the feature breaks outside local dev. It also treats 4xx/5xx payloads as successful image blobs and can leave loading stuck on network failures.

Suggested fix
 export default function SketchTool() {
+  const API_URL =
+    process.env.NEXT_PUBLIC_API_URL || "http://localhost:5000";
   const [file, setFile] = useState<File | null>(null);
   const [preview, setPreview] = useState<string | null>(null);
   const [result, setResult] = useState<string | null>(null);
   const [loading, setLoading] = useState(false);
@@
   const applySketch = async () => {
     if (!file) return;
 
     setLoading(true);
 
     const formData = new FormData();
     formData.append("image", file);
 
-    const res = await fetch("http://localhost:5000/api/images/sketch", {
-      method: "POST",
-      body: formData
-    });
-
-    const blob = await res.blob();
-    const url = URL.createObjectURL(blob);
-
-    setResult(url);
-    setLoading(false);
+    try {
+      const res = await fetch(`${API_URL}/api/images/sketch`, {
+        method: "POST",
+        body: formData,
+      });
+
+      if (!res.ok) {
+        throw new Error(await res.text());
+      }
+
+      const blob = await res.blob();
+      setResult((prev) => {
+        if (prev) URL.revokeObjectURL(prev);
+        return URL.createObjectURL(blob);
+      });
+    } finally {
+      setLoading(false);
+    }
   };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/tools/sketch/page.tsx` around lines 20 - 37, applySketch
currently POSTs to a hardcoded "http://localhost:5000/api/images/sketch" and
treats any response as an image blob; update applySketch to use a configurable
API base (e.g., read from an env/client config) instead of the hardcoded
localhost, immediately check res.ok after fetch and throw or handle non-OK
responses so errors don't return blobs, ensure setLoading(false) is called in
all exit paths (on success, non-OK responses, and network exceptions), and
surface or log the error before returning so callers can react; key symbols to
change: applySketch, fetch call, res.ok check, setLoading, setResult.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
frontend/app/tools/page.tsx (1)

84-87: Use a sketch-specific icon instead of QrCode for the Pencil Sketch card.

Lines 77 and 84 both use the QrCode icon, but "Pencil Sketch" needs visual distinction from "Image → QR" to be scannable in the grid. Replace with Pencil (available in lucide-react):

Suggested change
 import {
     Crop,
     Image as ImageIcon,
     ImageOff,
     Layers,
     Maximize2,
-    Minimize2, Palette, QrCode,
+    Minimize2, Palette, Pencil, QrCode,
     ShieldOff,
     SlidersHorizontal,
     Type,
     Wand2
 } from "lucide-react";
@@
         {
-            icon: <QrCode className="w-6 h-6" />,
+            icon: <Pencil className="w-6 h-6" />,
             color: "rgba(163,163,163,1)",
             label: "Pencil Sketch",
             href: "/tools/sketch"
         },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/tools/page.tsx` around lines 84 - 87, Replace the QrCode icon
used for the card whose label is "Pencil Sketch" by swapping the icon reference
in its object: change icon: <QrCode .../> to icon: <Pencil className="w-6 h-6"
/> and add Pencil to the lucide-react imports at the top of the file (keeping
the existing className and props unchanged) so the "Pencil Sketch" card uses the
Pencil icon instead of QrCode.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@frontend/app/tools/page.tsx`:
- Around line 84-87: Replace the QrCode icon used for the card whose label is
"Pencil Sketch" by swapping the icon reference in its object: change icon:
<QrCode .../> to icon: <Pencil className="w-6 h-6" /> and add Pencil to the
lucide-react imports at the top of the file (keeping the existing className and
props unchanged) so the "Pencil Sketch" card uses the Pencil icon instead of
QrCode.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f4d67bae-65c7-4826-955e-5fb1c2e3bb20

📥 Commits

Reviewing files that changed from the base of the PR and between 8df4026 and e523010.

📒 Files selected for processing (1)
  • frontend/app/tools/page.tsx

@ASP-31 ASP-31 changed the title Sketching Feature Add pencil sketch image transformation tool Mar 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants