Compare commits
No commits in common. "44c0f27d37aea94cdf97c4d6baf92992075d7296" and "3d14178546cd2ca02c45bf524a32216d89f1c029" have entirely different histories.
44c0f27d37
...
3d14178546
2 changed files with 34 additions and 41 deletions
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "image-speech-bubble-transformer",
|
"name": "image-speech-bubble-transformer",
|
||||||
"version": "1.1.0",
|
"version": "1.0.3",
|
||||||
"description": "TypeScript library for applying speech bubble effects to images",
|
"description": "TypeScript library for applying speech bubble effects to images",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
|
|
73
src/index.ts
73
src/index.ts
|
@ -1,8 +1,8 @@
|
||||||
import { mkdir, writeFile } from "fs/promises";
|
import { PathLike } from "fs";
|
||||||
import * as path from "path";
|
import fs from "fs/promises";
|
||||||
|
import path from "path";
|
||||||
import sharp from "sharp";
|
import sharp from "sharp";
|
||||||
|
|
||||||
// Supported image formats
|
|
||||||
const SUPPORTED_FORMATS = [
|
const SUPPORTED_FORMATS = [
|
||||||
".png",
|
".png",
|
||||||
".jpg",
|
".jpg",
|
||||||
|
@ -13,7 +13,6 @@ const SUPPORTED_FORMATS = [
|
||||||
".tiff",
|
".tiff",
|
||||||
];
|
];
|
||||||
|
|
||||||
// Orientation types
|
|
||||||
export enum Orientation {
|
export enum Orientation {
|
||||||
TOP = 1,
|
TOP = 1,
|
||||||
LEFT = 2,
|
LEFT = 2,
|
||||||
|
@ -21,7 +20,6 @@ export enum Orientation {
|
||||||
RIGHT = 4,
|
RIGHT = 4,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interface for library options
|
|
||||||
export interface SpeechBubbleOptions {
|
export interface SpeechBubbleOptions {
|
||||||
mirror?: boolean;
|
mirror?: boolean;
|
||||||
orientation?: Orientation;
|
orientation?: Orientation;
|
||||||
|
@ -87,44 +85,44 @@ export class ImageSpeechBubbleTransformer {
|
||||||
inputBuffer: Buffer,
|
inputBuffer: Buffer,
|
||||||
options: SpeechBubbleOptions = {}
|
options: SpeechBubbleOptions = {}
|
||||||
): Promise<Buffer> {
|
): Promise<Buffer> {
|
||||||
// Validate input
|
|
||||||
if (!inputBuffer) {
|
if (!inputBuffer) {
|
||||||
throw new Error("Input buffer is required");
|
throw new Error("Input buffer is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use default speech bubble path if not provided
|
|
||||||
const speechBubblePath =
|
const speechBubblePath =
|
||||||
options.speechBubblePath || this.defaultSpeechBubblePath;
|
options.speechBubblePath || this.defaultSpeechBubblePath;
|
||||||
|
|
||||||
// Check file formats
|
|
||||||
this.checkFileFormat(speechBubblePath);
|
this.checkFileFormat(speechBubblePath);
|
||||||
|
|
||||||
// Get input image metadata
|
const speechBubbleBuffer = await sharp(speechBubblePath).toBuffer();
|
||||||
const inputMetadata = await sharp(inputBuffer).metadata();
|
|
||||||
|
|
||||||
// Read and resize speech bubble to match input image
|
|
||||||
const speechBubbleBuffer = await sharp(speechBubblePath)
|
|
||||||
.resize({
|
|
||||||
width: inputMetadata.width,
|
|
||||||
height: inputMetadata.height,
|
|
||||||
fit: sharp.fit.cover,
|
|
||||||
position: sharp.strategy.entropy,
|
|
||||||
})
|
|
||||||
.toBuffer();
|
|
||||||
|
|
||||||
// Process main image
|
|
||||||
const processedImage = await sharp(inputBuffer)
|
const processedImage = await sharp(inputBuffer)
|
||||||
.ensureAlpha() // Ensure alpha channel
|
.ensureAlpha()
|
||||||
.composite([
|
.metadata()
|
||||||
{
|
.then(async (metadata) => {
|
||||||
input: await this.transformImage(speechBubbleBuffer, {
|
// match image size
|
||||||
|
const resizedSpeechBubble = await sharp(speechBubbleBuffer)
|
||||||
|
.resize(metadata.width, metadata.height)
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
const transformedSpeechBubble = await this.transformImage(
|
||||||
|
resizedSpeechBubble,
|
||||||
|
{
|
||||||
mirror: options.mirror,
|
mirror: options.mirror,
|
||||||
orientation: options.orientation,
|
orientation: options.orientation,
|
||||||
}),
|
}
|
||||||
blend: "xor",
|
);
|
||||||
},
|
|
||||||
])
|
// subtract speech bubble
|
||||||
.toBuffer();
|
return sharp(inputBuffer)
|
||||||
|
.composite([
|
||||||
|
{
|
||||||
|
input: transformedSpeechBubble,
|
||||||
|
blend: "difference",
|
||||||
|
},
|
||||||
|
])
|
||||||
|
.toBuffer();
|
||||||
|
});
|
||||||
|
|
||||||
return processedImage;
|
return processedImage;
|
||||||
}
|
}
|
||||||
|
@ -134,11 +132,10 @@ export class ImageSpeechBubbleTransformer {
|
||||||
*/
|
*/
|
||||||
async processAndSave(
|
async processAndSave(
|
||||||
inputBuffer: Buffer,
|
inputBuffer: Buffer,
|
||||||
outputPath: string,
|
outputPath: PathLike,
|
||||||
options: SpeechBubbleOptions = {}
|
options: SpeechBubbleOptions = {}
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
// Check output file format
|
this.checkFileFormat(outputPath.toString());
|
||||||
this.checkFileFormat(outputPath);
|
|
||||||
|
|
||||||
// Process image
|
// Process image
|
||||||
const processedBuffer = await this.processSpeechBubble(
|
const processedBuffer = await this.processSpeechBubble(
|
||||||
|
@ -146,17 +143,13 @@ export class ImageSpeechBubbleTransformer {
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
|
|
||||||
// Ensure output directory exists
|
const outputDir = path.dirname(outputPath.toString());
|
||||||
const outputDir = path.dirname(outputPath);
|
await fs.mkdir(outputDir, { recursive: true });
|
||||||
await mkdir(outputDir, { recursive: true });
|
|
||||||
|
|
||||||
// Save processed image
|
await sharp(processedBuffer).toFile(outputPath.toString());
|
||||||
await writeFile(outputPath, processedBuffer);
|
|
||||||
console.log(`Image saved at ${path.resolve(outputPath)}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Example usage
|
|
||||||
export function createSpeechBubbleTransformer(assetsDir?: string) {
|
export function createSpeechBubbleTransformer(assetsDir?: string) {
|
||||||
return new ImageSpeechBubbleTransformer(assetsDir);
|
return new ImageSpeechBubbleTransformer(assetsDir);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue