mirror of
https://github.com/Brooooooklyn/Image.git
synced 2025-12-08 18:36:03 +00:00
feat(image): implement overlay (#33)
* feat(image): implement `overlay` * ci: fix x86_64-pc-windows-msvc build
This commit is contained in:
parent
6a095ccd5d
commit
8bcc5de976
2
.github/workflows/CI.yml
vendored
2
.github/workflows/CI.yml
vendored
@ -33,7 +33,7 @@ jobs:
|
||||
strip -x packages/*/*.node
|
||||
- host: windows-latest
|
||||
build: |
|
||||
choco install meson -y
|
||||
python -m pip install meson
|
||||
yarn workspace @napi-rs/image build --features with_simd
|
||||
target: x86_64-pc-windows-msvc
|
||||
- host: ubuntu-latest
|
||||
|
||||
@ -166,4 +166,11 @@ writeFileSync(
|
||||
)
|
||||
|
||||
console.info(chalk.green('Encoding webp from JPEG with EXIF done'))
|
||||
|
||||
writeFileSync(
|
||||
'output-overlay-png.png',
|
||||
await new Transformer(PNG).overlay(PNG, 200, 200).png()
|
||||
)
|
||||
|
||||
console.info(chalk.green('Overlay an image done'))
|
||||
```
|
||||
|
||||
@ -64,3 +64,10 @@ writeFileSync(
|
||||
)
|
||||
|
||||
console.info(chalk.green('Encoding webp from JPEG with EXIF done'))
|
||||
|
||||
writeFileSync(
|
||||
'output-overlay-png.png',
|
||||
await new Transformer(PNG).overlay(PNG, 200, 200).png()
|
||||
)
|
||||
|
||||
console.info(chalk.green('Overlay an image done'))
|
||||
@ -419,6 +419,28 @@ export const enum ResizeFilterType {
|
||||
}
|
||||
```
|
||||
|
||||
#### `overlay`
|
||||
|
||||
```ts
|
||||
/**
|
||||
* Overlay an image at a given coordinate (x, y)
|
||||
*/
|
||||
overlay(onTop: Buffer, x: number, y: number): this
|
||||
```
|
||||
```ts
|
||||
import { writeFileSync } from 'fs'
|
||||
|
||||
import { Transformer } from '@napi-rs/image'
|
||||
|
||||
|
||||
const imageOutputPng = await new Transformer(PNG).overlay(PNG, 200, 200).png()
|
||||
|
||||
writeFileSync(
|
||||
'output-overlay-png.png',
|
||||
imageOutputPng
|
||||
)
|
||||
```
|
||||
|
||||
**ResizeFilterType**:
|
||||
|
||||
To test the different sampling filters on a real example, you can find two
|
||||
|
||||
2
packages/binding/index.d.ts
vendored
2
packages/binding/index.d.ts
vendored
@ -300,6 +300,8 @@ export interface Metadata {
|
||||
}
|
||||
export class Transformer {
|
||||
constructor(input: Buffer)
|
||||
/** Overlay an image at a given coordinate (x, y) */
|
||||
overlay(onTop: Buffer, x: number, y: number): this
|
||||
static fromRgbaPixels(input: Buffer | Uint8ClampedArray, width: number, height: number): Transformer
|
||||
metadata(withExif?: boolean | undefined | null, signal?: AbortSignal | undefined | null): Promise<Metadata>
|
||||
/**
|
||||
|
||||
@ -2,6 +2,7 @@ use std::collections::HashMap;
|
||||
use std::io::Cursor;
|
||||
use std::sync::Arc;
|
||||
|
||||
use image::imageops::overlay;
|
||||
use image::{
|
||||
imageops::FilterType, ColorType, DynamicImage, ImageBuffer, ImageEncoder, ImageFormat,
|
||||
};
|
||||
@ -70,7 +71,7 @@ impl TryFrom<u16> for Orientation {
|
||||
8 => Ok(Orientation::Rotate270Cw),
|
||||
_ => Err(Error::new(
|
||||
Status::InvalidArg,
|
||||
format!("Invalid orientation {}", value),
|
||||
format!("Invalid orientation {value}"),
|
||||
)),
|
||||
}
|
||||
}
|
||||
@ -214,7 +215,7 @@ impl ThreadSafeDynamicImage {
|
||||
let image_format = image::guess_format(input_buf).map_err(|err| {
|
||||
Error::new(
|
||||
Status::InvalidArg,
|
||||
format!("Guess format from input image failed {}", err),
|
||||
format!("Guess format from input image failed {err}"),
|
||||
)
|
||||
})?;
|
||||
if with_exif {
|
||||
@ -227,7 +228,7 @@ impl ThreadSafeDynamicImage {
|
||||
let avif = libavif::decode_rgb(input_buf).map_err(|err| {
|
||||
Error::new(
|
||||
Status::InvalidArg,
|
||||
format!("Decode avif image failed {}", err),
|
||||
format!("Decode avif image failed {err}"),
|
||||
)
|
||||
})?;
|
||||
let decoded_rgb = avif.to_vec();
|
||||
@ -249,7 +250,7 @@ impl ThreadSafeDynamicImage {
|
||||
}
|
||||
} else {
|
||||
image::load_from_memory_with_format(input_buf, image_format)
|
||||
.map_err(|err| Error::new(Status::InvalidArg, format!("Decode image failed {}", err)))?
|
||||
.map_err(|err| Error::new(Status::InvalidArg, format!("Decode image failed {err}")))?
|
||||
};
|
||||
let color_type = dynamic_image.color();
|
||||
image.replace(ImageMetaData {
|
||||
@ -476,6 +477,7 @@ impl Task for EncodeTask {
|
||||
if let Some((x, y, width, height)) = self.image_transform_args.crop {
|
||||
meta.image = meta.image.crop_imm(x, y, width, height);
|
||||
}
|
||||
|
||||
let dynamic_image = &mut meta.image;
|
||||
let color_type = &meta.color_type;
|
||||
let width = dynamic_image.width();
|
||||
@ -521,7 +523,7 @@ impl Task for EncodeTask {
|
||||
.map_err(|err| {
|
||||
Error::new(
|
||||
Status::GenericFailure,
|
||||
format!("Encode output png failed {}", err),
|
||||
format!("Encode output png failed {err}"),
|
||||
)
|
||||
})?;
|
||||
return Ok(EncodeOutput::Buffer(output.into_inner()));
|
||||
@ -535,7 +537,7 @@ impl Task for EncodeTask {
|
||||
encoder.encode_image(dynamic_image).map_err(|err| {
|
||||
Error::new(
|
||||
Status::GenericFailure,
|
||||
format!("Encode output jpeg failed {}", err),
|
||||
format!("Encode output jpeg failed {err}"),
|
||||
)
|
||||
})?;
|
||||
return Ok(EncodeOutput::Buffer(output.into_inner()));
|
||||
@ -600,6 +602,16 @@ impl Transformer {
|
||||
}
|
||||
}
|
||||
|
||||
#[napi]
|
||||
/// Overlay an image at a given coordinate (x, y)
|
||||
pub fn overlay(&self, on_top: Buffer, x: i64, y: i64) -> Result<&Self> {
|
||||
let bottom = self.dynamic_image.get(true)?;
|
||||
let top = ThreadSafeDynamicImage::new(on_top);
|
||||
let top_image_meta = top.get(true)?;
|
||||
overlay(&mut bottom.image, &top_image_meta.image, x, y);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn from_rgba_pixels(
|
||||
input: Either<Buffer, Uint8ClampedArray>,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user