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
29 changes: 29 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# AGENTS.md

## Project Overview
`img-toolkit` is a Rust + WebAssembly image processing library published to npm.
The Rust core handles decode/transform/encode, and the TypeScript wrapper exposes the browser-facing API.

## Key Files
- `src/lib.rs`: Rust/WASM core pipeline (option parsing, resize/brightness, format encode/decode, Rust tests).
- `ts-wrapper/resizeImage.ts`: TypeScript public API wrapper used by app code.
- `example/imageWorker.js`: Worker-side integration example for async image processing.
- `Cargo.toml`: Rust crate config and dependencies.
- `package.json`: npm package metadata, scripts, and build entrypoints.
- `README.MD`: user-facing API docs and usage examples; update when behavior/API changes.

## Common Commands
- `cargo test`: run Rust unit tests for core behavior.
- `npm run build:wasm`: build wasm artifacts from Rust (`wasm-pack build --target web`).
- `npm run build:ts`: compile TypeScript wrapper/types.
- `npm run build`: full package build (`build:wasm` + `build:ts`).

Run the smallest relevant command first, then run the full build before final handoff.

## Working Rules
- Preserve 2.x API compatibility unless a breaking change is explicitly requested.
- Keep user-facing errors safe and generic; avoid exposing low-level internals.
- Validate/sanitize options (clamp ranges, handle non-finite values).
- Do not remove `jpg/jpeg`, `png`, or `webp` support unless explicitly requested.
- Keep changes focused; avoid unrelated refactors.
- If API/default behavior changes, update `README.MD` and relevant examples in `example/`.
27 changes: 23 additions & 4 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Demo: https://2yh02.github.io/img-toolkit
- [Usage](#usage)
- [Functions](#functions)
- [Options](#options)
- [Defaults and Input Validation](#defaults-and-input-validation)
- [Error Handling Policy](#error-handling-policy)
- [Quality Behavior](#quality-behavior)
- [Quality Comparison](#quality-comparison)
- [Vite Setup for WASM](#vite-setup-for-wasm)
Expand Down Expand Up @@ -95,7 +97,7 @@ adjustBrightness(file: File, options: BrightnessOptions): Promise<File>
| ------------ | ------ | ------------------------------------------------------------------------------------------------- |
| `width` | number | (Optional) Target width in pixels. If omitted, width is auto-adjusted. |
| `height` | number | (Optional) Target height in pixels. If omitted, height is auto-adjusted. |
| `quality` | number | (Optional) 0.0 to 1.0. Effective for JPEG and WebP output. |
| `quality` | number | (Optional) 0.0 to 1.0. Effective for JPEG and WebP output. Defaults to `0.7`. Non-finite values (e.g. `NaN`) are sanitized to the default. |
| `format` | string | Output format (`"jpg"`, `"png"`, `"webp"`). |
| `brightness` | number | (Optional) 0.0 to 1.0. Defaults to 0.5. |
| `resampling` | number | (Optional) 0 to 10. Defaults to 4. |
Expand All @@ -107,22 +109,39 @@ adjustBrightness(file: File, options: BrightnessOptions): Promise<File>
| `width` | number | (Optional) Target width in pixels. |
| `height` | number | (Optional) Target height in pixels. |
| `resampling` | number | (Optional) 0 to 10. Defaults to 4. |
| `quality` | number | (Optional) 0.0 to 1.0. Effective if source is JPEG. |
| `quality` | number | (Optional) 0.0 to 1.0. Effective if source is JPEG. Defaults to `0.7`. Non-finite values are sanitized to the default. |

### `ConvertFormatOptions`

| Option | Type | Description |
| --------- | ------ | ------------------------------------------------------ |
| `format` | string | Output format (`"jpg"`, `"png"`, `"webp"`). |
| `quality` | number | (Optional) 0.0 to 1.0. Effective for JPEG and WebP output. |
| `quality` | number | (Optional) 0.0 to 1.0. Effective for JPEG and WebP output. Defaults to `0.7`. Non-finite values are sanitized to the default. |

### `BrightnessOptions`

| Option | Type | Description |
| ------------ | ------ | --------------------------------------------------- |
| `brightness` | number | 0.0 to 1.0 |
| `brightness` | number | 0.0 to 1.0. Defaults to `0.5`. Non-finite values are sanitized to the default. |
| `quality` | number | (Optional) 0.0 to 1.0. Effective if source is JPEG. |

## Defaults and Input Validation

- `quality` defaults to `0.7` when omitted.
- `brightness` defaults to `0.5` when omitted.
- `resampling` defaults to `4` when omitted.
- `quality` and `brightness` are clamped to valid ranges (`0.0..1.0`), and non-finite values such as `NaN` / `Infinity` are sanitized to safe defaults.
- `resampling` is clamped to `0..10`, and non-finite values are sanitized to default.

## Error Handling Policy

- User-facing errors are intentionally generic and safe for client contexts.
- Internal low-level encoder/decoder details are logged internally and are not returned directly to API consumers.
- Typical user-facing messages include:
- `Invalid options`
- `Unsupported format`
- `Image processing failed`

## Quality Behavior

- `jpg` output: `quality` is applied.
Expand Down
59 changes: 59 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,65 @@ mod tests {
assert!(matches!(err, ToolkitError::UnsupportedFormat));
}

#[test]
fn resize_image_without_dimensions_keeps_original_size() {
let input = make_test_png(120, 80);
let options = ResizeOptions {
width: None,
height: None,
quality: None,
format: "png".to_string(),
brightness: 0.5,
resampling: 4,
};

let output = resize_image_with_options(&input, options).unwrap();
let (format, decoded) = decode_image(&output);

assert_eq!(format, ImageFormat::Png);
assert_eq!(decoded.dimensions(), (120, 80));
}

#[test]
fn resize_image_returns_decode_error_for_invalid_input_bytes() {
let input = vec![0x00, 0x11, 0x22, 0x33];
let options = ResizeOptions {
width: Some(32),
height: Some(32),
quality: None,
format: "jpg".to_string(),
brightness: 0.5,
resampling: 4,
};

let err = resize_image_with_options(&input, options).unwrap_err();
assert!(
matches!(
err,
ToolkitError::FormatGuessFailed(_) | ToolkitError::DecodeFailed(_)
)
);
}

#[test]
fn resize_image_encodes_as_webp() {
let input = make_test_png(96, 64);
let options = ResizeOptions {
width: Some(48),
height: Some(32),
quality: Some(0.7),
format: "webp".to_string(),
brightness: 0.5,
resampling: 4,
};

let output = resize_image_with_options(&input, options).unwrap();
let (format, decoded) = decode_image(&output);

assert_eq!(format, ImageFormat::WebP);
assert_eq!(decoded.dimensions(), (48, 32));
}

#[test]
fn toolkit_error_user_messages_follow_exposure_policy() {
assert_eq!(ToolkitError::InvalidOptions("x".to_string()).user_message(), "Invalid options");
Expand Down
2 changes: 1 addition & 1 deletion ts-wrapper/resizeImage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export type ConvertFormatOptions = {
};

export type BrightnessOptions = {
brightness: number;
brightness?: number;
/**
* 0.0 to 1.0. Effective when the source image is JPEG.
*/
Expand Down