Skip to content
Open
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
194 changes: 194 additions & 0 deletions ARCHITECTURE_AND_PRODUCTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
# Image Manipulator: Architecture, How It Works, and Productionization Guide

## 1) What this project is

This is a Java image processing app with three entry modes:
- **Batch script mode** (`-file`): executes commands from a script file.
- **Interactive text mode** (`-text`): executes commands entered in a REPL-like console.
- **GUI mode** (no args): launches a Swing interface with buttons for operations.

The design follows an MVC shape:
- **Model**: image state + image operations.
- **Controllers**: parse/route user actions to model operations.
- **Views**: text output or Swing UI rendering.

## 2) Runtime flow

### Program startup
`ImageProcessingProgram.main` creates one `ImageModelImpl` and then chooses a controller/view pair based on args.

- `-file`: reads a script file into a `Readable`, starts text controller.
- `-text`: reads from stdin, starts text controller.
- no args: starts Swing GUI view + GUI controller.

### Text mode flow
1. `ImageTextControllerImpl` initializes a command map from command string -> command object constructor.
2. In `start()`, it loops through tokens from `Scanner`.
3. It ignores comment lines beginning with `#`.
4. `q`/`quit` exits.
5. Known commands build an `ImageCommand` and call `execute(model)`.
6. Unknown/invalid command sequences are rendered as errors.

### GUI mode flow
1. `ImageGUIViewImpl` constructs Swing components (buttons, image panel, histogram panel).
2. `ImageGUIControllerImpl` registers as an `ActionListener`.
3. Clicking a button triggers `actionPerformed`, which dispatches to helpers (`brightenHelper`, `flipHelper`, etc.).
4. Operations are always performed on a single model key: `"guiImage"`.
5. After each successful op, `updateView()` refreshes displayed image + histogram.

## 3) Core domain model internals

## Image storage strategy
`ImageModelImpl` stores images in `HashMap<String, Image>`.

Benefits:
- Easy named checkpoints in text mode (`source`, `source-blur`, etc.).
- Constant-time lookup by logical name.

Tradeoff:
- In-memory only; no persistence or eviction strategy.

## Operation delegation
`ImageModelImpl` does orchestration and delegates pixel math to `ImageImpl`:
- component greyscale
- brighten/darken
- flips
- blur/sharpen (convolution)
- greyscale/sepia (3x3 color transform)
- downsize

This keeps controller logic thin and image math encapsulated.

## Pixel/image immutability pattern
`ImageImpl` fields are final (`width`, `height`, `maxValue`, `pixels`), and most ops create new pixel arrays and return new `ImageImpl` instances.

Notable detail: `getPixels()` deep-copies pixel values into new `RGBPixel` objects before returning, so callers don’t mutate internal arrays directly.

## 4) Supported operations and algorithm sketch

- **Greyscale component**: compute one scalar per pixel from channel/value/intensity/luma and write to R=G=B.
- **Brighten/darken**: add/subtract scalar per channel with clamping.
- **Flip**: swap pairs across horizontal or vertical axis.
- **Blur/sharpen**: convolution over each pixel with fixed kernel.
- **Color transformation**: multiply pixel RGB vector by 3x3 matrix.
- **Downsize**: maps new coordinates to old image; if non-integer, averages surrounding neighbors.

Complexity rough order:
- Per-pixel ops (brighten, component, transform): **O(W×H)**.
- Convolution (K×K kernel): **O(W×H×K²)**.
- Downsize: about **O(newW×newH)** with local neighborhood sampling.

## 5) File I/O architecture

The command layer chooses reader/writer by extension:
- `Load`/`Save` switch on last 3 chars (`ppm`, `png`, `jpg`, `bmp`).

I/O strategy classes:
- `AbstractImageInputOutput`: common `ImageIO`-based load/save (BMP/PNG/JPG).
- `PPMImageInputOutput`: custom ASCII P3 parser/writer.

This is a clean “strategy-like” structure, but extension detection can be made more robust (e.g., case-insensitive, full suffix parsing, MIME sniffing).

## 6) GUI subsystem behavior

`ImageGUIViewImpl` is straightforward Swing:
- Left column: load/save + manipulation buttons.
- Right side: image preview in scroll pane + histogram panel.

`HistogramImpl` computes 256-bin frequencies for red/green/blue/intensity and draws polylines.

## 7) Current strengths

- Good educational MVC separation.
- Command pattern for text mode is easy to extend.
- Clear operation library in model/image layer.
- Good test suite footprint in `test/`.

## 8) Current bottlenecks / risks for scaling

1. **Single-process, in-memory design**
- No persistence, caching, or streaming.
- Large images can stress heap.

2. **Synchronous execution**
- Heavy ops run inline; no background jobs.
- GUI may feel blocked on large images.

3. **Error handling consistency**
- Some places throw; some print to stdout.
- No typed error model or error codes.

4. **Extension-based format detection**
- Last-3-char parsing is brittle (`jpeg`, uppercase, malformed names).

5. **Limited observability**
- No structured logs, metrics, tracing, profiling hooks.

6. **No deployment boundary**
- This is a desktop app architecture, not a service API architecture.

## 9) How to productionize (practical staged roadmap)

### Stage A: Hardening in current architecture
- Add a build tool (`Gradle` or `Maven`) and pin dependencies.
- Add CI: compile + unit tests + static analysis.
- Standardize exceptions and user-facing messages.
- Add image size guards + memory limits.
- Improve file type handling (`Path`, robust extension parsing).

### Stage B: Performance & correctness
- Replace exception-driven bounds handling in convolution with explicit boundary checks.
- Add benchmark tests (small/medium/large images).
- Introduce optional parallel processing for independent rows/tiles.
- Add undo stack for GUI and memory-aware eviction policy.

### Stage C: Service/API extraction
Split into modules:
1. `image-core` (pure operations, no UI)
2. `image-io` (formats + decoding)
3. `image-api` (REST/gRPC endpoints)
4. `image-worker` (async job execution)
5. `image-ui` (optional web/desktop client)

Then expose APIs like:
- `POST /images` upload
- `POST /images/{id}/operations` run one op
- `POST /pipelines` run multiple ops
- `GET /jobs/{id}` poll async job status
- `GET /images/{id}` download output

### Stage D: Cloud scale patterns
- **Storage**: object storage for originals/outputs.
- **Queue**: async jobs via message queue.
- **Workers**: autoscaled stateless processors.
- **DB**: metadata (job state, user ownership, audit trail).
- **CDN**: serve generated assets.
- **Caching**: deduplicate repeated transformations.

### Stage E: Security & multi-tenant readiness
- AuthN/AuthZ (JWT/OAuth).
- Rate limits + quotas.
- Malware scanning and file validation on upload.
- Strict resource ceilings per job.
- Full audit logging.

## 10) Concrete design recommendations if you were starting this as a startup product

- Keep this repo’s **operation logic** as the nucleus.
- Build a stateless processing microservice around it.
- Treat each transform request as an immutable job.
- Store images outside process memory and pass references.
- Make the UI a client, not the compute host.
- Add product features: operation history, presets, batch pipelines, webhooks.

## 11) “If I had 2 weeks” implementation plan

1. Add Maven + JUnit runner + CI pipeline.
2. Extract `model/image` into a reusable library module.
3. Add a minimal Spring Boot API with upload + apply-op + download.
4. Use local disk/S3-compatible storage abstraction.
5. Add async execution with a worker thread pool.
6. Add health endpoint + logs + basic Prometheus metrics.
7. Containerize with Docker and ship a compose stack.

That gives you a credible v1 production architecture while preserving your current logic.
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
### **Description** <br>
Image Manipulator is a Java image processing application with text-based, GUI, and command line functionality. It currently contains 10+ features including: uploading an image, saving an image to a device, flipping (vertical and horizontal), brightening/dimming, greyscaling, blurring, sharpening, applying a sepia filter, and downsizing an image. The application currently allows users to load or save an image in the formats .jpg, jpng, .ppm, and .bmp. When using the GUI, a histogram also displays the frequencies of each value (between 0-255) for the red, green, and blue values of each pixel. All functionality has been properly tested with JUnit.

Additional modern features include edge detection, auto contrast stretching, and 90° rotation in both directions.

### **Final Product** <br>
The GUI is shown below.

Expand Down Expand Up @@ -75,6 +77,18 @@ The lines above the scripts represent explanations of what the scripts themselv
####applying a sepia tone to the image of the family <br>
`sepia family family-sepia`

####detect edges in the image of the family <br>
`edge-detect family family-edges`

####apply automatic per-channel contrast stretching to the image of the family <br>
`auto-contrast family family-contrast`

####rotate the image clockwise by 90 degrees <br>
`rotate-clockwise family family-rotated-cw`

####rotate the image counterclockwise by 90 degrees <br>
`rotate-counterclockwise family family-rotated-ccw`

####save family-brighter in the ppm format in the res/family/ directory <br>
`save res/family/family-brighter.ppm family-brighter`

Expand Down Expand Up @@ -108,4 +122,4 @@ The lines above the scripts represent explanations of what the scripts themselv
####q to quit the program <br>
`q`
<br>
<br>
<br>
13 changes: 12 additions & 1 deletion USEME.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,18 @@ Click the button to apply the sepia color transformation to the image.
### **Downsize**
Click the button and enter positive integers for how much you would like to decrease the width
and height percentage of the image.

### **Edge Detect**
Click the button to highlight image edges.

### **Auto Contrast**
Click the button to stretch contrast across RGB channels automatically.

### **Rotate Clockwise**
Click the button to rotate the image by 90 degrees clockwise.

### **Rotate Counterclockwise**
Click the button to rotate the image by 90 degrees counterclockwise.
<br>

### **Saving an Image**
Expand All @@ -81,4 +93,3 @@ Make sure that you adhere to the appropriate constraints when performing an imag
#### Aryan Kale - kale.ar@northeastern.edu <br>
#### Anshul Shirude - shirude.a@northeastern.edu <br>


26 changes: 26 additions & 0 deletions src/controller/commands/AutoContrast.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package controller.commands;

import model.ImageModel;
import model.image.Image;

/**
* Automatically stretches contrast for all channels independently.
*/
public class AutoContrast extends AbstractImageCommand {

/**
* Constructs this command.
*
* @param in the image name to adjust
* @param out the name for the contrast-adjusted image
*/
public AutoContrast(String in, String out) {
super(in, out);
}

@Override
public void execute(ImageModel model) {
Image image = model.getImage(in);
model.setImage(out, ImageTransformUtil.autoContrast(image));
}
}
29 changes: 29 additions & 0 deletions src/controller/commands/EdgeDetect.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package controller.commands;

import model.ImageModel;

/**
* Applies a Laplacian edge-detection kernel to the image.
*/
public class EdgeDetect extends AbstractImageCommand {

/**
* Constructs this command.
*
* @param in the image name to detect edges on
* @param out the name for the edge-detected image
*/
public EdgeDetect(String in, String out) {
super(in, out);
}

@Override
public void execute(ImageModel model) {
double[][] kernel = new double[][]
{{-1, -1, -1},
{-1, 8, -1},
{-1, -1, -1}};

model.setImage(out, model.getImage(in).filter(kernel));
}
}
Loading