31 Commits

Author SHA1 Message Date
2eb887dce7 feat(core): add quality parameter for image compression control 2025-08-02 17:17:52 +00:00
764b074ca4 feat(core): add progressive JPEG support and fix Sharp format switching 2025-08-02 15:18:04 +00:00
c919507375 update description 2024-05-29 14:13:46 +02:00
68e4d9cc74 update tsconfig 2024-04-14 17:44:47 +02:00
19b723146b update npmextra.json: githost 2024-04-01 21:35:40 +02:00
cc540ad39e update npmextra.json: githost 2024-04-01 19:58:32 +02:00
0b53a9514a update npmextra.json: githost 2024-03-30 21:47:30 +01:00
a2b408a7bf 1.0.18 2024-02-14 01:56:00 +01:00
67fdcf0362 fix(core): update 2024-02-14 01:56:00 +01:00
c3f840e1be 1.0.17 2023-12-03 23:32:48 +01:00
6840d94517 fix(core): update 2023-12-03 23:32:47 +01:00
4600cde549 1.0.16 2023-12-03 23:30:58 +01:00
33e8ba5f30 fix(core): update 2023-12-03 23:30:58 +01:00
8566c5f57f 1.0.15 2023-12-03 23:09:49 +01:00
4cc5accff1 fix(core): update 2023-12-03 23:09:48 +01:00
2b82a4b74a 1.0.14 2023-12-03 19:04:37 +01:00
499b7e5f42 fix(core): update 2023-12-03 19:04:36 +01:00
6b1948d834 1.0.13 2023-12-03 18:57:45 +01:00
7b340ea783 fix(core): update 2023-12-03 18:57:44 +01:00
063905bfaa 1.0.12 2023-12-03 18:41:44 +01:00
f1ad45289a fix(core): update 2023-12-03 18:41:43 +01:00
193e174fd7 1.0.11 2023-12-03 18:10:36 +01:00
703d21c426 fix(core): update 2023-12-03 18:10:35 +01:00
2d83ebac4d 1.0.10 2023-12-03 16:35:52 +01:00
7454a52d03 fix(core): update 2023-12-03 16:35:51 +01:00
1e93429186 1.0.9 2023-12-03 16:32:53 +01:00
ee926e18a7 fix(core): update 2023-12-03 16:32:52 +01:00
02a6656869 1.0.8 2023-11-24 20:21:13 +01:00
b07aba76e9 fix(core): update 2023-11-24 20:21:12 +01:00
7fdeed160a 1.0.7 2023-11-24 20:09:24 +01:00
c6cd1dc38f fix(core): update 2023-11-24 20:09:23 +01:00
16 changed files with 9393 additions and 4562 deletions

View File

@@ -0,0 +1,66 @@
name: Default (not tags)
on:
push:
tags-ignore:
- '**'
env:
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
NPMCI_URL_CLOUDLY: ${{secrets.NPMCI_URL_CLOUDLY}}
jobs:
security:
runs-on: ubuntu-latest
continue-on-error: true
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Install pnpm and npmci
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
- name: Run npm prepare
run: npmci npm prepare
- name: Audit production dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --prod
continue-on-error: true
- name: Audit development dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --dev
continue-on-error: true
test:
if: ${{ always() }}
needs: security
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Test stable
run: |
npmci node install stable
npmci npm install
npmci npm test
- name: Test build
run: |
npmci node install stable
npmci npm install
npmci npm build

View File

@@ -0,0 +1,124 @@
name: Default (tags)
on:
push:
tags:
- '*'
env:
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
NPMCI_URL_CLOUDLY: ${{secrets.NPMCI_URL_CLOUDLY}}
jobs:
security:
runs-on: ubuntu-latest
continue-on-error: true
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
npmci npm prepare
- name: Audit production dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --prod
continue-on-error: true
- name: Audit development dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --dev
continue-on-error: true
test:
if: ${{ always() }}
needs: security
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
npmci npm prepare
- name: Test stable
run: |
npmci node install stable
npmci npm install
npmci npm test
- name: Test build
run: |
npmci node install stable
npmci npm install
npmci npm build
release:
needs: test
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
npmci npm prepare
- name: Release
run: |
npmci node install stable
npmci npm publish
metadata:
needs: test
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
continue-on-error: true
steps:
- uses: actions/checkout@v3
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
npmci npm prepare
- name: Code quality
run: |
npmci command npm install -g typescript
npmci npm install
- name: Trigger
run: npmci trigger
- name: Build docs and upload artifacts
run: |
npmci node install stable
npmci npm install
pnpm install -g @git.zone/tsdoc
npmci command tsdoc
continue-on-error: true

View File

@@ -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

41
changelog.md Normal file
View File

@@ -0,0 +1,41 @@
# Changelog
## [1.2.0] - 2025-01-02
### Added
- Quality parameter support for image compression
- New `quality` option (1-100) in IAssetVariation interface for controlling compression quality
- Quality control implementation for both Sharp and Jimp modes
- Support for quality settings on all lossy formats (JPEG, WebP, AVIF)
- Enhanced documentation with quality control examples
### Changed
- Jimp mode now uses fall-through pattern in format switch for cleaner code
- Console logging when Jimp falls back to JPEG for unsupported formats (WebP, AVIF)
- Updated examples in documentation to showcase quality parameter usage
## [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

View File

@@ -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
of this software and associated documentation files (the "Software"), to deal

View File

@@ -2,17 +2,33 @@
"gitzone": {
"projectType": "npm",
"module": {
"githost": "gitlab.com",
"githost": "code.foss.global",
"gitscope": "push.rocks",
"gitrepo": "smartjimp",
"description": "a tool fr working with images in TypeScript",
"description": "A TypeScript library for image processing combining both sharp and jimp libraries.",
"npmPackagename": "@push.rocks/smartjimp",
"license": "MIT",
"projectDomain": "push.rocks"
"projectDomain": "push.rocks",
"keywords": [
"image processing",
"TypeScript",
"sharp",
"jimp",
"resize image",
"convert image format",
"image caching",
"buffer manipulation",
"AVIF",
"WEBP",
"PNG"
]
}
},
"npmci": {
"npmGlobalTools": [],
"npmAccessLevel": "public"
},
"tsdoc": {
"legal": "\n## License and Legal Information\n\nThis repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy 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.\n"
}
}

View File

@@ -1,8 +1,8 @@
{
"name": "@push.rocks/smartjimp",
"version": "1.0.6",
"version": "1.2.0",
"private": false,
"description": "a tool fr working with images in TypeScript",
"description": "A TypeScript library for image processing combining both sharp and jimp libraries.",
"main": "dist_ts/index.js",
"typings": "dist_ts/index.d.ts",
"author": "Lossless GmbH",
@@ -10,23 +10,23 @@
"scripts": {
"test": "(tstest test/)",
"build": "(tsbuild --allowimplicitany)",
"buildDocs": "tsdoc"
"buildDocs": "tsdoc",
"localPublish": "gitzone commit && pnpm run build && pnpm publish && pnpm publish --access public --registry=\"https://registry.npmjs.org\""
},
"devDependencies": {
"@git.zone/tsbuild": "^2.1.66",
"@git.zone/tsbuild": "^2.1.72",
"@git.zone/tsrun": "^1.2.44",
"@git.zone/tstest": "^1.0.77",
"@push.rocks/tapbundle": "^5.0.8",
"@types/node": "^20.10.0"
"@git.zone/tstest": "^2.3.2",
"@types/node": "^20.11.17"
},
"dependencies": {
"@push.rocks/levelcache": "^3.0.3",
"@push.rocks/levelcache": "^3.0.8",
"@push.rocks/smartfile": "^11.0.4",
"@push.rocks/smarthash": "^3.0.4",
"@push.rocks/smartpath": "^5.0.5",
"@push.rocks/smartrequest": "^2.0.15",
"@types/sharp": "^0.32.0",
"sharp": "^0.32.6"
"@push.rocks/smartpath": "^6.0.0",
"@push.rocks/smartrequest": "^4.2.1",
"jimp": "^1.6.0",
"sharp": "^0.34.3"
},
"files": [
"ts/**/*",
@@ -43,5 +43,27 @@
"type": "module",
"browserslist": [
"last 1 chrome versions"
]
],
"repository": {
"type": "git",
"url": "https://code.foss.global/push.rocks/smartjimp.git"
},
"bugs": {
"url": "https://gitlab.com/push.rocks/smartjimp/issues"
},
"homepage": "https://code.foss.global/push.rocks/smartjimp",
"keywords": [
"image processing",
"TypeScript",
"sharp",
"jimp",
"resize image",
"convert image format",
"image caching",
"buffer manipulation",
"AVIF",
"WEBP",
"PNG"
],
"packageManager": "pnpm@10.11.0+sha512.6540583f41cc5f628eb3d9773ecee802f4f9ef9923cc45b69890fb47991d4b092964694ec3a4f738a420c918a333062c8b925d312f42e4f0c263eb603551f977"
}

12849
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

1
readme.hints.md Normal file
View File

@@ -0,0 +1 @@

429
readme.md
View File

@@ -1,37 +1,410 @@
# @push.rocks/smartjimp
a tool fr working with images in TypeScript
# @push.rocks/smartjimp 🖼️
## Availabililty and Links
* [npmjs.org (npm package)](https://www.npmjs.com/package/@push.rocks/smartjimp)
* [gitlab.com (source)](https://gitlab.com/pushrocks/smartjimp)
* [github.com (source mirror)](https://github.com/pushrocks/smartjimp)
* [docs (typedoc)](https://pushrocks.gitlab.io/smartjimp/)
**Lightning-fast image processing for TypeScript, powered by Sharp and Jimp**
## Status for master
[![npm version](https://img.shields.io/npm/v/@push.rocks/smartjimp.svg)](https://www.npmjs.com/package/@push.rocks/smartjimp)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.x-blue.svg)](https://www.typescriptlang.org/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./license)
Status Category | Status Badge
-- | --
GitLab Pipelines | [![pipeline status](https://gitlab.com/pushrocks/smartjimp/badges/master/pipeline.svg)](https://lossless.cloud)
GitLab Pipline Test Coverage | [![coverage report](https://gitlab.com/pushrocks/smartjimp/badges/master/coverage.svg)](https://lossless.cloud)
npm | [![npm downloads per month](https://badgen.net/npm/dy/@push.rocks/smartjimp)](https://lossless.cloud)
Snyk | [![Known Vulnerabilities](https://badgen.net/snyk/pushrocks/smartjimp)](https://lossless.cloud)
TypeScript Support | [![TypeScript](https://badgen.net/badge/TypeScript/>=%203.x/blue?icon=typescript)](https://lossless.cloud)
node Support | [![node](https://img.shields.io/badge/node->=%2010.x.x-blue.svg)](https://nodejs.org/dist/latest-v10.x/docs/api/)
Code Style | [![Code Style](https://badgen.net/badge/style/prettier/purple)](https://lossless.cloud)
PackagePhobia (total standalone install weight) | [![PackagePhobia](https://badgen.net/packagephobia/install/@push.rocks/smartjimp)](https://lossless.cloud)
PackagePhobia (package size on registry) | [![PackagePhobia](https://badgen.net/packagephobia/publish/@push.rocks/smartjimp)](https://lossless.cloud)
BundlePhobia (total size when bundled) | [![BundlePhobia](https://badgen.net/bundlephobia/minzip/@push.rocks/smartjimp)](https://lossless.cloud)
## Why SmartJimp? 🚀
## Usage
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
Use TypeScript for best in class intellisense
Choose your engine based on your needs - same API, different performance characteristics. Plus, you get automatic caching out of the box!
## Contribution
## Installation 📦
We are always happy for code contributions. If you are not the code contributing type that is ok. Still, maintaining Open Source repositories takes considerable time and thought. If you like the quality of what we do and our modules are useful to you we would appreciate a little monthly contribution: You can [contribute one time](https://lossless.link/contribute-onetime) or [contribute monthly](https://lossless.link/contribute). :)
```bash
# Using npm
npm install @push.rocks/smartjimp --save
For further information read the linked docs at the top of this readme.
# Using pnpm (recommended)
pnpm add @push.rocks/smartjimp
## Legal
> MIT licensed | **©** [Task Venture Capital GmbH](https://task.vc)
| By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy)
# Using yarn
yarn add @push.rocks/smartjimp
```
## Quick Start 🏃‍♂️
```typescript
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 🎉
```
## Features ✨
### 🔄 Resize Images
Resize images while maintaining aspect ratio or set exact dimensions:
```typescript
import { SmartJimp } from '@push.rocks/smartjimp';
import { SmartFile } from '@push.rocks/smartfile';
const processor = new SmartJimp({ mode: 'sharp' });
// Load an image
const imageFile = await SmartFile.fromUrl('https://example.com/image.jpg');
// Resize to 500px width (height auto-calculated to maintain aspect ratio)
const resized = await processor.getFromSmartfile(imageFile, {
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();
```
### 🎨 Format Conversion
Convert between PNG, JPEG, WebP, and AVIF formats with ease:
```typescript
// Convert any image to PNG
const pngBuffer = await processor.getFromSmartfile(imageFile, {
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'
});
```
### 🎚️ Quality Control
Fine-tune image compression quality for the perfect balance between file size and visual fidelity:
```typescript
// High quality JPEG (larger file size)
const highQualityJpeg = await processor.getFromSmartfile(imageFile, {
format: 'jpeg',
quality: 90 // 1-100, higher = better quality
});
// Optimized for web (smaller file size)
const webOptimized = await processor.getFromSmartfile(imageFile, {
format: 'jpeg',
quality: 75, // Good balance for web
progressive: true
});
// Ultra-compressed WebP
const tinyWebP = await processor.getFromSmartfile(imageFile, {
format: 'webp',
quality: 60 // WebP handles lower quality better than JPEG
});
// Quality works with all lossy formats
const qualityAvif = await processor.getFromSmartfile(imageFile, {
format: 'avif',
quality: 80 // AVIF provides excellent quality even at lower values
});
```
> 💡 **Pro tip**: Different formats handle quality settings differently. AVIF and WebP generally look better than JPEG at the same quality level.
### 📸 Progressive JPEG Support
Create progressive JPEGs that load in multiple passes for better perceived performance:
```typescript
// Create a progressive JPEG (Sharp mode only)
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
});
```
> 💡 **Note**: Progressive JPEG encoding is only supported in Sharp mode. Jimp will create standard JPEGs regardless of the progressive setting.
### 🔄 Image Effects
Apply visual effects to your images:
```typescript
// Invert colors (currently jimp-only)
const inverted = await processor.getFromSmartfile(imageFile, {
invert: true
});
```
### ⚡ Direct Buffer Processing
Work directly with buffers for maximum flexibility:
```typescript
// Process raw image buffers
const processedBuffer = await processor.computeAssetVariation(imageBuffer, {
width: 300,
format: 'webp',
quality: 85,
invert: true
});
// Create progressive JPEG from buffer
const progressiveBuffer = await processor.computeAssetVariation(imageBuffer, {
format: 'jpeg',
progressive: true,
quality: 88
});
// 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,
quality: 85 // AVIF excels at lower quality settings
}),
// Good browser support: WebP
webp: await processor.getFromSmartfile(sourceImage, {
format: 'webp',
width: 1200,
quality: 82 // WebP sweet spot for quality/size
}),
// Universal fallback: Progressive JPEG
jpeg: await processor.getFromSmartfile(sourceImage, {
format: 'jpeg',
progressive: true,
width: 1200,
quality: 80 // Standard web quality
})
};
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)
quality?: number; // Compression quality 1-100 (lossy formats 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
This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
### Trademarks
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.
### Company Information
Task Venture Capital GmbH
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.
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.

View File

@@ -1,11 +1,11 @@
import { expect, tap } from '@push.rocks/tapbundle';
import { expect, tap } from '@git.zone/tstest/tapbundle';
import * as smartjimp from '../ts/index.js';
import * as smartfile from '@push.rocks/smartfile';
let testSmartJimp: smartjimp.SmartJimp;
tap.test('first test', async () => {
testSmartJimp = new smartjimp.SmartJimp();
testSmartJimp = new smartjimp.SmartJimp({ mode: 'sharp'});
expect(testSmartJimp).toBeInstanceOf(smartjimp.SmartJimp);
});
@@ -15,4 +15,48 @@ tap.test('should be able to create a master', async () => {
(await smartfile.SmartFile.fromBuffer('.nogit/result.avif', convertedAsset)).write();
});
tap.test('should be able to use jimp', async () => {
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 convertedAsset = await testSmartJimp.computeAssetVariation(smartfileInstance.contentBuffer, {
format: 'png',
});
(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();

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@push.rocks/smartjimp',
version: '1.0.6',
version: '1.0.18',
description: 'a tool fr working with images in TypeScript'
}

View File

@@ -11,8 +11,3 @@ import * as smartpath from '@push.rocks/smartpath';
import * as smartrequest from '@push.rocks/smartrequest';
export { levelcache, smartpath, smarthash, smartfile, smartrequest };
// third party scope
import sharp from 'sharp';
export { sharp };

8
ts/plugins.typed.ts Normal file
View File

@@ -0,0 +1,8 @@
import type * as sharpType from 'sharp';
export { type sharpType };
import type { Jimp } from 'jimp';
import type * as jimpType from 'jimp';
export { type jimpType, type Jimp };

View File

@@ -1,8 +1,17 @@
import * as plugins from './smartjimp.plugins.js';
import * as plugins from './plugins.js';
import * as pluginsTyped from './plugins.typed.js';
export interface IDimensions {
export interface IAssetVariation {
format?: 'avif' | 'webp' | 'png' | 'jpeg';
width?: number;
height?: number;
invert?: boolean;
progressive?: boolean;
quality?: number;
}
export interface ISmartJimpOptions {
mode: 'sharp' | 'jimp';
}
export class SmartJimp {
@@ -12,53 +21,164 @@ export class SmartJimp {
maxDiskStorageInMB: 5000,
});
public options: ISmartJimpOptions;
constructor(optionsArg: ISmartJimpOptions) {
this.options = optionsArg;
}
/**
* get a key that is unique for a wanted asset variation
*/
private getCacheKey(
sourceTypeArg: 'streamfile' | 'smartfile',
sourceIdArg: string,
wantedDimensionsArg?: IDimensions
assetVariationArg?: IAssetVariation
) {
return `${sourceTypeArg}_${sourceIdArg}_${
wantedDimensionsArg
? `${wantedDimensionsArg.width || 'auto' }x${wantedDimensionsArg.height || 'auto'}`
assetVariationArg
? `${assetVariationArg.width || 'auto'}x${assetVariationArg.height || 'auto'}`
: 'original'
}`;
}
private async computeAssetVariation(assetBuffer: Buffer, wantedDimensions?: IDimensions) {
if (!wantedDimensions) {
return assetBuffer;
sharpMod: typeof pluginsTyped.sharpType.default;
public async getSharpMod(): Promise<
typeof pluginsTyped.sharpType.default
> {
if (!this.sharpMod) {
this.sharpMod = (await import('sharp')).default;
}
return this.sharpMod;
}
jimpMod: typeof pluginsTyped.jimpType;
public async getJimpMod(): Promise<typeof pluginsTyped.jimpType> {
if (!this.jimpMod) {
this.jimpMod = await import('jimp');
}
return this.jimpMod;
}
public async computeAssetVariation(assetBufferArg: Buffer, assetVariationArg: IAssetVariation) {
if (this.options.mode === 'sharp') {
const sharp = await this.getSharpMod();
if (!assetVariationArg) {
return assetBufferArg;
}
let sharpImage = sharp(assetBufferArg);
sharpImage = sharpImage.resize(assetVariationArg.width, assetVariationArg.height);
const resultResize = sharpImage.resize(assetVariationArg.width, assetVariationArg.height);
if (assetVariationArg.invert) {
// TODO: implement invert
}
switch (assetVariationArg.format) {
case 'avif':
const avifOptions: any = {};
if (assetVariationArg.quality !== undefined) {
avifOptions.quality = assetVariationArg.quality;
}
sharpImage = resultResize.avif(avifOptions);
break;
case 'webp':
const webpOptions: any = {};
if (assetVariationArg.quality !== undefined) {
webpOptions.quality = assetVariationArg.quality;
}
sharpImage = resultResize.webp(webpOptions);
break;
case 'png':
const pngOptions: any = {};
if (assetVariationArg.quality !== undefined) {
pngOptions.quality = assetVariationArg.quality;
}
sharpImage = resultResize.png(pngOptions);
break;
case 'jpeg':
const jpegOptions: any = {
progressive: assetVariationArg.progressive || false
};
if (assetVariationArg.quality !== undefined) {
jpegOptions.quality = assetVariationArg.quality;
}
sharpImage = resultResize.jpeg(jpegOptions);
break;
default:
// Default to JPEG
const defaultJpegOptions: any = {
progressive: assetVariationArg.progressive || false
};
if (assetVariationArg.quality !== undefined) {
defaultJpegOptions.quality = assetVariationArg.quality;
}
sharpImage = resultResize.jpeg(defaultJpegOptions);
}
return sharpImage.toBuffer();
} else if (this.options.mode === 'jimp') {
const jimpMod = await this.getJimpMod();
let jimpImage = await jimpMod.Jimp.read(assetBufferArg);
if (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) {
jimpImage.invert();
}
// Note: Jimp does not support progressive JPEG encoding
// Progressive option is ignored in jimp mode
const jpegOptions: any = {};
if (assetVariationArg.quality !== undefined) {
jpegOptions.quality = assetVariationArg.quality;
}
switch (assetVariationArg.format) {
case 'png':
return await jimpImage.getBuffer("image/png");
case 'webp':
case 'avif':
console.log(`Jimp doesn't support ${assetVariationArg.format}, falling back to JPEG`);
// Fall through to JPEG
case 'jpeg':
default:
// Default to JPEG
return await jimpImage.getBuffer("image/jpeg", jpegOptions);
}
}
let sharpImage = plugins.sharp(assetBuffer);
sharpImage = sharpImage.resize(wantedDimensions.width, wantedDimensions.height);
const result = await sharpImage.resize(wantedDimensions.width, wantedDimensions.height).avif().toBuffer();
return result;
}
public async getFromSmartfile(
smartfileArg: plugins.smartfile.SmartFile,
wantedDimensionsArg?: IDimensions
wantedDimensionsArg?: IAssetVariation
) {
const cacheKey = this.getCacheKey('smartfile', await smartfileArg.getHash(), wantedDimensionsArg);
const cacheKey = this.getCacheKey(
'smartfile',
await smartfileArg.getHash(),
wantedDimensionsArg
);
const existingCacheEntry = await this.levelCache.retrieveCacheEntryByKey(cacheKey);
if (existingCacheEntry) {
return existingCacheEntry.contents;
} else {
const computedAssetBuffer = await this.computeAssetVariation(smartfileArg.contentBuffer, wantedDimensionsArg);
this.levelCache.storeCacheEntryByKey(cacheKey, new plugins.levelcache.CacheEntry({
const computedAssetBuffer = await this.computeAssetVariation(
smartfileArg.contentBuffer,
wantedDimensionsArg
);
this.levelCache.storeCacheEntryByKey(
cacheKey,
new plugins.levelcache.CacheEntry({
contents: computedAssetBuffer,
ttl: 600000
}));
ttl: 600000,
})
);
return computedAssetBuffer;
}
}
public async createAvifImageFromBuffer(bufferArg: Buffer) {
const sharpImage = plugins.sharp(bufferArg);
const sharp = await this.getSharpMod();
const sharpImage = sharp(bufferArg);
const result = await sharpImage.avif().toBuffer();
return result;
}
}

View File

@@ -1,4 +1,4 @@
import * as plugins from './smartjimp.plugins.js';
import * as plugins from './plugins.js';
export const packageDir = plugins.path.join(plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url), '../');
export const nogitDir = plugins.path.join(packageDir, '.nogit/')