feat(core): add progressive JPEG support and fix Sharp format switching
This commit is contained in:
128
.gitlab-ci.yml
128
.gitlab-ci.yml
@@ -1,128 +0,0 @@
|
|||||||
# gitzone ci_default
|
|
||||||
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
|
||||||
|
|
||||||
cache:
|
|
||||||
paths:
|
|
||||||
- .npmci_cache/
|
|
||||||
key: '$CI_BUILD_STAGE'
|
|
||||||
|
|
||||||
stages:
|
|
||||||
- security
|
|
||||||
- test
|
|
||||||
- release
|
|
||||||
- metadata
|
|
||||||
|
|
||||||
before_script:
|
|
||||||
- pnpm install -g pnpm
|
|
||||||
- pnpm install -g @shipzone/npmci
|
|
||||||
- npmci npm prepare
|
|
||||||
|
|
||||||
# ====================
|
|
||||||
# security stage
|
|
||||||
# ====================
|
|
||||||
# ====================
|
|
||||||
# security stage
|
|
||||||
# ====================
|
|
||||||
auditProductionDependencies:
|
|
||||||
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
|
||||||
stage: security
|
|
||||||
script:
|
|
||||||
- npmci command npm config set registry https://registry.npmjs.org
|
|
||||||
- npmci command pnpm audit --audit-level=high --prod
|
|
||||||
tags:
|
|
||||||
- lossless
|
|
||||||
- docker
|
|
||||||
allow_failure: true
|
|
||||||
|
|
||||||
auditDevDependencies:
|
|
||||||
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
|
||||||
stage: security
|
|
||||||
script:
|
|
||||||
- npmci command npm config set registry https://registry.npmjs.org
|
|
||||||
- npmci command pnpm audit --audit-level=high --dev
|
|
||||||
tags:
|
|
||||||
- lossless
|
|
||||||
- docker
|
|
||||||
allow_failure: true
|
|
||||||
|
|
||||||
# ====================
|
|
||||||
# test stage
|
|
||||||
# ====================
|
|
||||||
|
|
||||||
testStable:
|
|
||||||
stage: test
|
|
||||||
script:
|
|
||||||
- npmci node install stable
|
|
||||||
- npmci npm install
|
|
||||||
- npmci npm test
|
|
||||||
coverage: /\d+.?\d+?\%\s*coverage/
|
|
||||||
tags:
|
|
||||||
- docker
|
|
||||||
|
|
||||||
testBuild:
|
|
||||||
stage: test
|
|
||||||
script:
|
|
||||||
- npmci node install stable
|
|
||||||
- npmci npm install
|
|
||||||
- npmci npm build
|
|
||||||
coverage: /\d+.?\d+?\%\s*coverage/
|
|
||||||
tags:
|
|
||||||
- docker
|
|
||||||
|
|
||||||
release:
|
|
||||||
stage: release
|
|
||||||
script:
|
|
||||||
- npmci node install stable
|
|
||||||
- npmci npm publish
|
|
||||||
only:
|
|
||||||
- tags
|
|
||||||
tags:
|
|
||||||
- lossless
|
|
||||||
- docker
|
|
||||||
- notpriv
|
|
||||||
|
|
||||||
# ====================
|
|
||||||
# metadata stage
|
|
||||||
# ====================
|
|
||||||
codequality:
|
|
||||||
stage: metadata
|
|
||||||
allow_failure: true
|
|
||||||
only:
|
|
||||||
- tags
|
|
||||||
script:
|
|
||||||
- npmci command npm install -g typescript
|
|
||||||
- npmci npm prepare
|
|
||||||
- npmci npm install
|
|
||||||
tags:
|
|
||||||
- lossless
|
|
||||||
- docker
|
|
||||||
- priv
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
stage: metadata
|
|
||||||
script:
|
|
||||||
- npmci trigger
|
|
||||||
only:
|
|
||||||
- tags
|
|
||||||
tags:
|
|
||||||
- lossless
|
|
||||||
- docker
|
|
||||||
- notpriv
|
|
||||||
|
|
||||||
pages:
|
|
||||||
stage: metadata
|
|
||||||
script:
|
|
||||||
- npmci node install stable
|
|
||||||
- npmci npm install
|
|
||||||
- npmci command npm run buildDocs
|
|
||||||
tags:
|
|
||||||
- lossless
|
|
||||||
- docker
|
|
||||||
- notpriv
|
|
||||||
only:
|
|
||||||
- tags
|
|
||||||
artifacts:
|
|
||||||
expire_in: 1 week
|
|
||||||
paths:
|
|
||||||
- public
|
|
||||||
allow_failure: true
|
|
27
changelog.md
Normal file
27
changelog.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## [1.1.0] - 2025-01-02
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Progressive JPEG support for Sharp mode
|
||||||
|
- New `jpeg` format option in IAssetVariation interface
|
||||||
|
- New `progressive` boolean option for creating progressive JPEGs
|
||||||
|
- Tests for progressive JPEG functionality
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixed Sharp switch statement missing break statements causing all formats to output as PNG
|
||||||
|
- Updated Jimp resize to properly maintain aspect ratio
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Updated dependencies:
|
||||||
|
- jimp: ^0.22.10 → ^1.6.0
|
||||||
|
- sharp: ^0.33.2 → ^0.34.3
|
||||||
|
- @push.rocks/smartpath: ^5.0.5 → ^6.0.0
|
||||||
|
- @push.rocks/smartrequest: ^2.0.15 → ^4.2.1
|
||||||
|
- @git.zone/tstest: ^1.0.86 → ^2.3.2
|
||||||
|
- Both Sharp and Jimp modes now default to JPEG when no format is specified
|
||||||
|
- Updated documentation to include progressive JPEG examples
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
- Progressive JPEG encoding is only supported in Sharp mode
|
||||||
|
- Jimp mode will create standard JPEGs regardless of the progressive setting
|
2
license
2
license
@@ -1,4 +1,4 @@
|
|||||||
Copyright (c) 2020 Lossless GmbH (hello@lossless.com)
|
Copyright (c) 2020 Task Venture Capital GmbH (hello@lossless.com)
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
18
package.json
18
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@push.rocks/smartjimp",
|
"name": "@push.rocks/smartjimp",
|
||||||
"version": "1.0.18",
|
"version": "1.1.0",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "A TypeScript library for image processing combining both sharp and jimp libraries.",
|
"description": "A TypeScript library for image processing combining both sharp and jimp libraries.",
|
||||||
"main": "dist_ts/index.js",
|
"main": "dist_ts/index.js",
|
||||||
@@ -16,18 +16,17 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@git.zone/tsbuild": "^2.1.72",
|
"@git.zone/tsbuild": "^2.1.72",
|
||||||
"@git.zone/tsrun": "^1.2.44",
|
"@git.zone/tsrun": "^1.2.44",
|
||||||
"@git.zone/tstest": "^1.0.86",
|
"@git.zone/tstest": "^2.3.2",
|
||||||
"@push.rocks/tapbundle": "^5.0.8",
|
|
||||||
"@types/node": "^20.11.17"
|
"@types/node": "^20.11.17"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@push.rocks/levelcache": "^3.0.8",
|
"@push.rocks/levelcache": "^3.0.8",
|
||||||
"@push.rocks/smartfile": "^11.0.4",
|
"@push.rocks/smartfile": "^11.0.4",
|
||||||
"@push.rocks/smarthash": "^3.0.4",
|
"@push.rocks/smarthash": "^3.0.4",
|
||||||
"@push.rocks/smartpath": "^5.0.5",
|
"@push.rocks/smartpath": "^6.0.0",
|
||||||
"@push.rocks/smartrequest": "^2.0.15",
|
"@push.rocks/smartrequest": "^4.2.1",
|
||||||
"jimp": "^0.22.10",
|
"jimp": "^1.6.0",
|
||||||
"sharp": "^0.33.2"
|
"sharp": "^0.34.3"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"ts/**/*",
|
"ts/**/*",
|
||||||
@@ -65,5 +64,6 @@
|
|||||||
"AVIF",
|
"AVIF",
|
||||||
"WEBP",
|
"WEBP",
|
||||||
"PNG"
|
"PNG"
|
||||||
]
|
],
|
||||||
}
|
"packageManager": "pnpm@10.11.0+sha512.6540583f41cc5f628eb3d9773ecee802f4f9ef9923cc45b69890fb47991d4b092964694ec3a4f738a420c918a333062c8b925d312f42e4f0c263eb603551f977"
|
||||||
|
}
|
||||||
|
13274
pnpm-lock.yaml
generated
13274
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
369
readme.md
369
readme.md
@@ -1,89 +1,356 @@
|
|||||||
# @push.rocks/smartjimp
|
# @push.rocks/smartjimp 🖼️
|
||||||
a tool fr working with images in TypeScript
|
|
||||||
|
|
||||||
## Install
|
**Lightning-fast image processing for TypeScript, powered by Sharp and Jimp**
|
||||||
|
|
||||||
To install `@push.rocks/smartjimp`, use the following command in your project directory:
|
[](https://www.npmjs.com/package/@push.rocks/smartjimp)
|
||||||
|
[](https://www.typescriptlang.org/)
|
||||||
|
[](./license)
|
||||||
|
|
||||||
|
## Why SmartJimp? 🚀
|
||||||
|
|
||||||
|
SmartJimp bridges the gap between two powerful image processing libraries:
|
||||||
|
- **Sharp** 🗡️ - Blazing fast, native C++ bindings, perfect for production
|
||||||
|
- **Jimp** 🎨 - Pure JavaScript, runs anywhere Node.js does
|
||||||
|
|
||||||
|
Choose your engine based on your needs - same API, different performance characteristics. Plus, you get automatic caching out of the box!
|
||||||
|
|
||||||
|
## Installation 📦
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# Using npm
|
||||||
npm install @push.rocks/smartjimp --save
|
npm install @push.rocks/smartjimp --save
|
||||||
|
|
||||||
|
# Using pnpm (recommended)
|
||||||
|
pnpm add @push.rocks/smartjimp
|
||||||
|
|
||||||
|
# Using yarn
|
||||||
|
yarn add @push.rocks/smartjimp
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Quick Start 🏃♂️
|
||||||
|
|
||||||
`@push.rocks/smartjimp` is a comprehensive TypeScript library for handling image processing with ease and efficiency. It leverages powerful image processing libraries such as `sharp` and `jimp`, providing a unified interface for performing common image manipulation tasks. This documentation aims to guide you through the installation and various functionalities of `@push.rocks/smartjimp`, ensuring you have the knowledge to integrate image processing capabilities into your TypeScript project effectively.
|
|
||||||
|
|
||||||
### Getting Started
|
|
||||||
|
|
||||||
First, import the `SmartJimp` class from the package:
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { SmartJimp } from '@push.rocks/smartjimp';
|
import { SmartJimp } from '@push.rocks/smartjimp';
|
||||||
|
|
||||||
|
// Choose your engine
|
||||||
|
const imageProcessor = new SmartJimp({ mode: 'sharp' }); // or 'jimp'
|
||||||
|
|
||||||
|
// That's it! You're ready to process images 🎉
|
||||||
```
|
```
|
||||||
|
|
||||||
Next, create an instance of `SmartJimp` by specifying the preferred image processing mode (`sharp` or `jimp`):
|
## Features ✨
|
||||||
|
|
||||||
```typescript
|
### 🔄 Resize Images
|
||||||
const imageProcessor = new SmartJimp({ mode: 'sharp' });
|
Resize images while maintaining aspect ratio or set exact dimensions:
|
||||||
```
|
|
||||||
|
|
||||||
### Processing Images
|
|
||||||
|
|
||||||
#### From Buffer
|
|
||||||
|
|
||||||
You can process images from a buffer. This is particularly useful when you have image data already available in your application, such as uploaded images.
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
|
import { SmartJimp } from '@push.rocks/smartjimp';
|
||||||
import { SmartFile } from '@push.rocks/smartfile';
|
import { SmartFile } from '@push.rocks/smartfile';
|
||||||
|
|
||||||
// Assuming you have an image buffer `imageBuffer`
|
const processor = new SmartJimp({ mode: 'sharp' });
|
||||||
let imageBuffer: Buffer;
|
|
||||||
|
|
||||||
// Convert the buffer to a SmartFile instance
|
// Load an image
|
||||||
const smartfileInstance = await SmartFile.fromBuffer(imageBuffer);
|
const imageFile = await SmartFile.fromUrl('https://example.com/image.jpg');
|
||||||
|
|
||||||
// Process the image, for example, resizing it to width 500px
|
// Resize to 500px width (height auto-calculated to maintain aspect ratio)
|
||||||
const resizedImageBuffer = await imageProcessor.getFromSmartfile(smartfileInstance, {
|
const resized = await processor.getFromSmartfile(imageFile, {
|
||||||
width: 500,
|
width: 500
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Or resize to exact dimensions
|
||||||
|
const exactSize = await processor.getFromSmartfile(imageFile, {
|
||||||
|
width: 800,
|
||||||
|
height: 600
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save the result
|
||||||
|
await SmartFile.fromBuffer('./resized.jpg', resized).write();
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Creating Specific Formats
|
### 🎨 Format Conversion
|
||||||
|
Convert between PNG, JPEG, WebP, and AVIF formats with ease:
|
||||||
`@push.rocks/smartjimp` supports creating images in various formats, including AVIF, WebP, and PNG.
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// For forcing a format, specify the `format` in the options, like 'png'
|
// Convert any image to PNG
|
||||||
const pngBuffer = await imageProcessor.getFromSmartfile(smartfileInstance, {
|
const pngBuffer = await processor.getFromSmartfile(imageFile, {
|
||||||
width: 500,
|
format: 'png'
|
||||||
format: 'png',
|
});
|
||||||
|
|
||||||
|
// Convert to standard JPEG
|
||||||
|
const jpegBuffer = await processor.getFromSmartfile(imageFile, {
|
||||||
|
format: 'jpeg'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Convert to WebP for smaller file sizes
|
||||||
|
const webpBuffer = await processor.getFromSmartfile(imageFile, {
|
||||||
|
format: 'webp',
|
||||||
|
width: 1200 // Resize while converting!
|
||||||
|
});
|
||||||
|
|
||||||
|
// Convert to AVIF for next-gen compression (Sharp only)
|
||||||
|
const avifBuffer = await processor.getFromSmartfile(imageFile, {
|
||||||
|
format: 'avif'
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### Advanced Image Manipulation
|
### 📸 Progressive JPEG Support
|
||||||
|
Create progressive JPEGs that load in multiple passes for better perceived performance:
|
||||||
#### Inversion
|
|
||||||
|
|
||||||
You can invert the colors of an image as follows:
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
const invertedImageBuffer = await imageProcessor.getFromSmartfile(smartfileInstance, {
|
// Create a progressive JPEG (Sharp mode only)
|
||||||
invert: true,
|
const progressiveJpeg = await processor.getFromSmartfile(imageFile, {
|
||||||
|
format: 'jpeg',
|
||||||
|
progressive: true,
|
||||||
|
width: 800
|
||||||
|
});
|
||||||
|
|
||||||
|
// Convert PNG to progressive JPEG
|
||||||
|
const pngFile = await SmartFile.fromUrl('https://example.com/image.png');
|
||||||
|
const progressiveFromPng = await processor.getFromSmartfile(pngFile, {
|
||||||
|
format: 'jpeg',
|
||||||
|
progressive: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Standard JPEG (non-progressive)
|
||||||
|
const standardJpeg = await processor.getFromSmartfile(imageFile, {
|
||||||
|
format: 'jpeg',
|
||||||
|
progressive: false // default
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### Performance and Caching
|
> 💡 **Note**: Progressive JPEG encoding is only supported in Sharp mode. Jimp will create standard JPEGs regardless of the progressive setting.
|
||||||
|
|
||||||
`@push.rocks/smartjimp` employs caching mechanisms to enhance performance. Caching is handled internally, ensuring that repeated processing requests for the same image and settings are served faster by retrieving the processed image from the cache.
|
### 🔄 Image Effects
|
||||||
|
Apply visual effects to your images:
|
||||||
|
|
||||||
### Mode: Sharp vs. Jimp
|
```typescript
|
||||||
|
// Invert colors (currently jimp-only)
|
||||||
|
const inverted = await processor.getFromSmartfile(imageFile, {
|
||||||
|
invert: true
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
- **Sharp**: Recommended for performance-critical applications. It is faster and supports a broader range of image formats. However, it relies on native libraries, which may need additional setup in certain environments.
|
### ⚡ Direct Buffer Processing
|
||||||
- **Jimp**: A pure JavaScript image processing library. While not as fast as Sharp, it guarantees compatibility across all Node.js environments without the need for additional native dependencies.
|
Work directly with buffers for maximum flexibility:
|
||||||
|
|
||||||
### Conclusion
|
```typescript
|
||||||
|
// Process raw image buffers
|
||||||
|
const processedBuffer = await processor.computeAssetVariation(imageBuffer, {
|
||||||
|
width: 300,
|
||||||
|
format: 'webp',
|
||||||
|
invert: true
|
||||||
|
});
|
||||||
|
|
||||||
`@push.rocks/smartjimp` makes image processing in TypeScript applications highly accessible and efficient. Whether you need to resize, convert formats, or perform other image manipulations, `@push.rocks/smartjimp` provides a powerful yet simple API to accomplish these tasks with minimal effort. By abstracting away the complexities of direct interactions with libraries like `sharp` and `jimp`, it allows developers to focus more on building the core features of their applications.
|
// Create progressive JPEG from buffer
|
||||||
|
const progressiveBuffer = await processor.computeAssetVariation(imageBuffer, {
|
||||||
|
format: 'jpeg',
|
||||||
|
progressive: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Special AVIF creation method (sharp mode only)
|
||||||
|
const avifBuffer = await processor.createAvifImageFromBuffer(imageBuffer);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🚀 Built-in Caching
|
||||||
|
SmartJimp automatically caches processed images for lightning-fast repeated operations:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// First call: processes the image
|
||||||
|
const result1 = await processor.getFromSmartfile(imageFile, { width: 500 });
|
||||||
|
|
||||||
|
// Second call: returns from cache instantly! ⚡
|
||||||
|
const result2 = await processor.getFromSmartfile(imageFile, { width: 500 });
|
||||||
|
```
|
||||||
|
|
||||||
|
Cache features:
|
||||||
|
- Automatic cache key generation based on source and parameters
|
||||||
|
- 10-minute default TTL
|
||||||
|
- Memory + disk hybrid caching with LevelCache
|
||||||
|
|
||||||
|
## Sharp vs Jimp: Choose Your Fighter 🥊
|
||||||
|
|
||||||
|
### Use Sharp when:
|
||||||
|
- 🏃♂️ **Performance is critical** - Up to 10x faster than Jimp
|
||||||
|
- 🎯 **You need AVIF support** - Sharp exclusive feature
|
||||||
|
- 📸 **You need progressive JPEGs** - Sharp exclusive feature
|
||||||
|
- 🖥️ **Running on servers** - Where native dependencies are OK
|
||||||
|
- 📦 **Processing many images** - Batch operations benefit from speed
|
||||||
|
|
||||||
|
### Use Jimp when:
|
||||||
|
- 🌍 **Cross-platform compatibility is key** - Pure JavaScript, no build steps
|
||||||
|
- ☁️ **Deploying to serverless** - Some platforms don't support native modules
|
||||||
|
- 🎨 **You need pixel-level manipulation** - Jimp has more pixel manipulation features
|
||||||
|
- 🛠️ **Simpler deployment** - No need to worry about sharp's native dependencies
|
||||||
|
|
||||||
|
## Advanced Examples 🎓
|
||||||
|
|
||||||
|
### Batch Processing with Async Operations
|
||||||
|
```typescript
|
||||||
|
import { SmartJimp } from '@push.rocks/smartjimp';
|
||||||
|
import { SmartFile } from '@push.rocks/smartfile';
|
||||||
|
|
||||||
|
const processor = new SmartJimp({ mode: 'sharp' });
|
||||||
|
|
||||||
|
// Process multiple images concurrently
|
||||||
|
const imageUrls = [
|
||||||
|
'https://example.com/photo1.jpg',
|
||||||
|
'https://example.com/photo2.jpg',
|
||||||
|
'https://example.com/photo3.jpg'
|
||||||
|
];
|
||||||
|
|
||||||
|
const processedImages = await Promise.all(
|
||||||
|
imageUrls.map(async (url) => {
|
||||||
|
const file = await SmartFile.fromUrl(url);
|
||||||
|
return processor.getFromSmartfile(file, {
|
||||||
|
width: 800,
|
||||||
|
format: 'jpeg',
|
||||||
|
progressive: true // Web-optimized progressive JPEGs
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Save all processed images
|
||||||
|
await Promise.all(
|
||||||
|
processedImages.map((buffer, index) =>
|
||||||
|
SmartFile.fromBuffer(`./output/image-${index}.jpg`, buffer).write()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Creating Responsive Image Sets
|
||||||
|
```typescript
|
||||||
|
// Generate multiple sizes for responsive images
|
||||||
|
const sizes = [320, 640, 1024, 1920];
|
||||||
|
const formats = ['webp', 'jpeg'] as const;
|
||||||
|
|
||||||
|
for (const size of sizes) {
|
||||||
|
for (const format of formats) {
|
||||||
|
const processed = await processor.getFromSmartfile(originalImage, {
|
||||||
|
width: size,
|
||||||
|
format,
|
||||||
|
// Use progressive for JPEGs
|
||||||
|
progressive: format === 'jpeg'
|
||||||
|
});
|
||||||
|
|
||||||
|
await SmartFile.fromBuffer(
|
||||||
|
`./responsive/image-${size}.${format}`,
|
||||||
|
processed
|
||||||
|
).write();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Web-Optimized Image Pipeline
|
||||||
|
```typescript
|
||||||
|
// Create a complete image optimization pipeline
|
||||||
|
async function optimizeForWeb(sourceImage: SmartFile) {
|
||||||
|
const processor = new SmartJimp({ mode: 'sharp' });
|
||||||
|
|
||||||
|
// Create multiple formats for browser compatibility
|
||||||
|
const formats = {
|
||||||
|
// Modern browsers: AVIF
|
||||||
|
avif: await processor.getFromSmartfile(sourceImage, {
|
||||||
|
format: 'avif',
|
||||||
|
width: 1200
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Good browser support: WebP
|
||||||
|
webp: await processor.getFromSmartfile(sourceImage, {
|
||||||
|
format: 'webp',
|
||||||
|
width: 1200
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Universal fallback: Progressive JPEG
|
||||||
|
jpeg: await processor.getFromSmartfile(sourceImage, {
|
||||||
|
format: 'jpeg',
|
||||||
|
progressive: true,
|
||||||
|
width: 1200
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
return formats;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Reference 📚
|
||||||
|
|
||||||
|
### Constructor Options
|
||||||
|
```typescript
|
||||||
|
interface ISmartJimpOptions {
|
||||||
|
mode: 'sharp' | 'jimp'; // Choose your processing engine
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Processing Options
|
||||||
|
```typescript
|
||||||
|
interface IAssetVariation {
|
||||||
|
format?: 'avif' | 'webp' | 'png' | 'jpeg'; // Output format
|
||||||
|
width?: number; // Target width (maintains aspect ratio if height not set)
|
||||||
|
height?: number; // Target height (maintains aspect ratio if width not set)
|
||||||
|
invert?: boolean; // Invert colors (jimp only currently)
|
||||||
|
progressive?: boolean; // Create progressive JPEG (sharp only, jpeg format only)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Main Methods
|
||||||
|
|
||||||
|
#### `getFromSmartfile(smartfile, options?)`
|
||||||
|
Process a SmartFile instance with caching enabled.
|
||||||
|
|
||||||
|
#### `computeAssetVariation(buffer, options)`
|
||||||
|
Process a raw buffer without caching.
|
||||||
|
|
||||||
|
#### `createAvifImageFromBuffer(buffer)`
|
||||||
|
Create an AVIF image from buffer (sharp mode only).
|
||||||
|
|
||||||
|
## Performance Tips 🏎️
|
||||||
|
|
||||||
|
1. **Use Sharp mode in production** - It's significantly faster
|
||||||
|
2. **Leverage the built-in cache** - Process identical images only once
|
||||||
|
3. **Batch operations** - Use `Promise.all()` for concurrent processing
|
||||||
|
4. **Choose appropriate formats**:
|
||||||
|
- AVIF: Best compression, limited support
|
||||||
|
- WebP: Good compression, good support
|
||||||
|
- Progressive JPEG: Universal support, good perceived performance
|
||||||
|
- PNG: Lossless, larger files
|
||||||
|
|
||||||
|
## Troubleshooting 🔧
|
||||||
|
|
||||||
|
### Sharp installation issues
|
||||||
|
If you encounter issues with Sharp:
|
||||||
|
```bash
|
||||||
|
# Rebuild sharp from source
|
||||||
|
npm rebuild sharp
|
||||||
|
|
||||||
|
# Or install with specific platform
|
||||||
|
npm install sharp --platform=linux --arch=x64
|
||||||
|
```
|
||||||
|
|
||||||
|
### Memory usage
|
||||||
|
For processing many large images:
|
||||||
|
```typescript
|
||||||
|
// Process in batches to control memory usage
|
||||||
|
const batchSize = 10;
|
||||||
|
for (let i = 0; i < images.length; i += batchSize) {
|
||||||
|
const batch = images.slice(i, i + batchSize);
|
||||||
|
await processBatch(batch);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Progressive JPEG verification
|
||||||
|
To verify if a JPEG is progressive:
|
||||||
|
```bash
|
||||||
|
# Using ImageMagick
|
||||||
|
identify -verbose image.jpg | grep "Interlace"
|
||||||
|
# Output: "Interlace: JPEG" means progressive
|
||||||
|
|
||||||
|
# Using exiftool
|
||||||
|
exiftool -EncodingProcess image.jpg
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing 🤝
|
||||||
|
|
||||||
|
We welcome contributions! Please feel free to submit a Pull Request.
|
||||||
|
|
||||||
## License and Legal Information
|
## License and Legal Information
|
||||||
|
|
||||||
@@ -102,4 +369,4 @@ Registered at District court Bremen HRB 35230 HB, Germany
|
|||||||
|
|
||||||
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
|
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
|
||||||
|
|
||||||
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
39
test/test.ts
39
test/test.ts
@@ -1,4 +1,4 @@
|
|||||||
import { expect, tap } from '@push.rocks/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import * as smartjimp from '../ts/index.js';
|
import * as smartjimp from '../ts/index.js';
|
||||||
import * as smartfile from '@push.rocks/smartfile';
|
import * as smartfile from '@push.rocks/smartfile';
|
||||||
|
|
||||||
@@ -18,10 +18,45 @@ tap.test('should be able to create a master', async () => {
|
|||||||
tap.test('should be able to use jimp', async () => {
|
tap.test('should be able to use jimp', async () => {
|
||||||
const testSmartJimp = new smartjimp.SmartJimp({ mode: 'jimp'});
|
const testSmartJimp = new smartjimp.SmartJimp({ mode: 'jimp'});
|
||||||
const smartfileInstance = await smartfile.SmartFile.fromUrl('https://images.unsplash.com/photo-1673276628202-737bf3020ac2?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=3774&q=80')
|
const smartfileInstance = await smartfile.SmartFile.fromUrl('https://images.unsplash.com/photo-1673276628202-737bf3020ac2?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=3774&q=80')
|
||||||
const convertedAsset = await testSmartJimp.computeAssetVariation(smartfileInstance.contents, {
|
const convertedAsset = await testSmartJimp.computeAssetVariation(smartfileInstance.contentBuffer, {
|
||||||
format: 'png',
|
format: 'png',
|
||||||
});
|
});
|
||||||
(await smartfile.SmartFile.fromBuffer('.nogit/result2.png', convertedAsset)).write();
|
(await smartfile.SmartFile.fromBuffer('.nogit/result2.png', convertedAsset)).write();
|
||||||
})
|
})
|
||||||
|
|
||||||
|
tap.test('should create progressive JPEG with Sharp', async () => {
|
||||||
|
const testSmartJimp = new smartjimp.SmartJimp({ mode: 'sharp'});
|
||||||
|
const smartfileInstance = await smartfile.SmartFile.fromUrl('https://images.unsplash.com/photo-1673276628202-737bf3020ac2?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=3774&q=80')
|
||||||
|
|
||||||
|
// Create progressive JPEG
|
||||||
|
const progressiveJpeg = await testSmartJimp.getFromSmartfile(smartfileInstance, {
|
||||||
|
format: 'jpeg',
|
||||||
|
progressive: true,
|
||||||
|
width: 800
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(progressiveJpeg).toBeInstanceOf(Buffer);
|
||||||
|
(await smartfile.SmartFile.fromBuffer('.nogit/progressive.jpg', progressiveJpeg)).write();
|
||||||
|
})
|
||||||
|
|
||||||
|
tap.test('should convert PNG to progressive JPEG', async () => {
|
||||||
|
const testSmartJimp = new smartjimp.SmartJimp({ mode: 'sharp'});
|
||||||
|
|
||||||
|
// First create a PNG
|
||||||
|
const smartfileInstance = await smartfile.SmartFile.fromUrl('https://images.unsplash.com/photo-1673276628202-737bf3020ac2?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=3774&q=80')
|
||||||
|
const pngBuffer = await testSmartJimp.computeAssetVariation(smartfileInstance.contentBuffer, {
|
||||||
|
format: 'png',
|
||||||
|
width: 500
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then convert it to progressive JPEG
|
||||||
|
const progressiveJpeg = await testSmartJimp.computeAssetVariation(pngBuffer, {
|
||||||
|
format: 'jpeg',
|
||||||
|
progressive: true
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(progressiveJpeg).toBeInstanceOf(Buffer);
|
||||||
|
(await smartfile.SmartFile.fromBuffer('.nogit/png-to-progressive.jpg', progressiveJpeg)).write();
|
||||||
|
})
|
||||||
|
|
||||||
tap.start();
|
tap.start();
|
||||||
|
@@ -2,6 +2,7 @@ import type * as sharpType from 'sharp';
|
|||||||
|
|
||||||
export { type sharpType };
|
export { type sharpType };
|
||||||
|
|
||||||
|
import type { Jimp } from 'jimp';
|
||||||
import type * as jimpType from 'jimp';
|
import type * as jimpType from 'jimp';
|
||||||
|
|
||||||
export { type jimpType };
|
export { type jimpType, type Jimp };
|
@@ -2,10 +2,11 @@ import * as plugins from './plugins.js';
|
|||||||
import * as pluginsTyped from './plugins.typed.js';
|
import * as pluginsTyped from './plugins.typed.js';
|
||||||
|
|
||||||
export interface IAssetVariation {
|
export interface IAssetVariation {
|
||||||
format?: 'avif' | 'webp' | 'png';
|
format?: 'avif' | 'webp' | 'png' | 'jpeg';
|
||||||
width?: number;
|
width?: number;
|
||||||
height?: number;
|
height?: number;
|
||||||
invert?: boolean;
|
invert?: boolean;
|
||||||
|
progressive?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISmartJimpOptions {
|
export interface ISmartJimpOptions {
|
||||||
@@ -50,11 +51,11 @@ export class SmartJimp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
jimpMod: typeof pluginsTyped.jimpType;
|
jimpMod: typeof pluginsTyped.jimpType;
|
||||||
public async getJimpMod(): Promise<typeof pluginsTyped.jimpType.default> {
|
public async getJimpMod(): Promise<typeof pluginsTyped.jimpType> {
|
||||||
if (!this.jimpMod) {
|
if (!this.jimpMod) {
|
||||||
this.jimpMod = await import('jimp');
|
this.jimpMod = await import('jimp');
|
||||||
}
|
}
|
||||||
return this.jimpMod.default;
|
return this.jimpMod;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async computeAssetVariation(assetBufferArg: Buffer, assetVariationArg: IAssetVariation) {
|
public async computeAssetVariation(assetBufferArg: Buffer, assetVariationArg: IAssetVariation) {
|
||||||
@@ -72,26 +73,53 @@ export class SmartJimp {
|
|||||||
switch (assetVariationArg.format) {
|
switch (assetVariationArg.format) {
|
||||||
case 'avif':
|
case 'avif':
|
||||||
sharpImage = resultResize.avif();
|
sharpImage = resultResize.avif();
|
||||||
|
break;
|
||||||
case 'webp':
|
case 'webp':
|
||||||
sharpImage = resultResize.webp();
|
sharpImage = resultResize.webp();
|
||||||
|
break;
|
||||||
case 'png':
|
case 'png':
|
||||||
sharpImage = resultResize.png();
|
sharpImage = resultResize.png();
|
||||||
|
break;
|
||||||
|
case 'jpeg':
|
||||||
|
sharpImage = resultResize.jpeg({
|
||||||
|
progressive: assetVariationArg.progressive || false
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Default to JPEG
|
||||||
|
sharpImage = resultResize.jpeg({
|
||||||
|
progressive: assetVariationArg.progressive || false
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return sharpImage.toBuffer();
|
return sharpImage.toBuffer();
|
||||||
} else if (this.options.mode === 'jimp') {
|
} else if (this.options.mode === 'jimp') {
|
||||||
const jimp = await this.getJimpMod();
|
const jimpMod = await this.getJimpMod();
|
||||||
let jimpImage = await jimp.read(assetBufferArg);
|
let jimpImage = await jimpMod.Jimp.read(assetBufferArg);
|
||||||
if (assetVariationArg.width || assetVariationArg.height) {
|
if (assetVariationArg.width || assetVariationArg.height) {
|
||||||
jimpImage = jimpImage.resize(assetVariationArg.width, assetVariationArg.height);
|
const resizeOptions: any = {};
|
||||||
|
if (assetVariationArg.width) resizeOptions.w = assetVariationArg.width;
|
||||||
|
if (assetVariationArg.height) resizeOptions.h = assetVariationArg.height;
|
||||||
|
jimpImage.resize(resizeOptions);
|
||||||
}
|
}
|
||||||
if (assetVariationArg.invert) {
|
if (assetVariationArg.invert) {
|
||||||
jimpImage = jimpImage.invert();
|
jimpImage.invert();
|
||||||
}
|
}
|
||||||
|
// Note: Jimp does not support progressive JPEG encoding
|
||||||
|
// Progressive option is ignored in jimp mode
|
||||||
switch (assetVariationArg.format) {
|
switch (assetVariationArg.format) {
|
||||||
case 'png':
|
case 'png':
|
||||||
return await jimpImage.getBufferAsync(jimp.MIME_PNG);
|
return await jimpImage.getBuffer("image/png");
|
||||||
|
case 'jpeg':
|
||||||
|
return await jimpImage.getBuffer("image/jpeg");
|
||||||
|
case 'webp':
|
||||||
|
// Jimp doesn't support WebP, fallback to JPEG
|
||||||
|
return await jimpImage.getBuffer("image/jpeg");
|
||||||
|
case 'avif':
|
||||||
|
// Jimp doesn't support AVIF, fallback to JPEG
|
||||||
|
return await jimpImage.getBuffer("image/jpeg");
|
||||||
default:
|
default:
|
||||||
return await jimpImage.getBufferAsync(jimp.MIME_JPEG);
|
// Default to JPEG
|
||||||
|
return await jimpImage.getBuffer("image/jpeg");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user