-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
All code refactored to use new API and hardening of Validators
- Loading branch information
Showing
20 changed files
with
946 additions
and
1,050 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,8 @@ | ||
export const MODES = ['horizontal', 'vertical', 'grid'] as const | ||
// export const MODES = ['horizontal', 'vertical', 'grid'] as const | ||
|
||
export const EXTENSIONS = ['jpg', 'jpeg', 'png', 'bmp', 'gif', 'tiff'] as const | ||
|
||
export const UNIQUE_REQUIREMENTS = ['all', 'distance', 'difference'] as const | ||
|
||
export const MAX_DISTANCE = 0.01 | ||
export const MAX_DIFFERENCE = 0.01 | ||
export const MAX_DIFFERENCE = 0.01 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,19 @@ | ||
import { ValiError } from "valibot" | ||
import { ValiError } from 'valibot' | ||
|
||
export class SpliteaError extends Error { | ||
constructor(msg: string) { | ||
constructor (msg: string) { | ||
super(msg) | ||
this.name = 'SpliteaError' | ||
} | ||
} | ||
|
||
export const ThrowSpliteaError = (error: any, msg: string): SpliteaError => { | ||
if (error instanceof SpliteaError) { return error } | ||
export const ThrowSpliteaError = (error: any, msg: string): never => { | ||
if (error instanceof SpliteaError) { throw error } | ||
if (error instanceof ValiError) { | ||
const err = error.issues[0].message | ||
const err: string = error.issues[0].message | ||
console.error(`Error: ${msg} -> ${err}`) | ||
return new SpliteaError(msg + '\n' + err) | ||
throw new SpliteaError(msg + '\n' + err) | ||
} | ||
console.error(error) | ||
return new SpliteaError(msg + '\n' + String(error)) | ||
throw new SpliteaError(msg + '\n' + String(error)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,96 +1,146 @@ | ||
import * as Path from 'node:path' | ||
import * as v from 'valibot' | ||
import Jimp from "jimp" | ||
import { SpliteaError, ThrowSpliteaError } from "./errors" | ||
import { Image, Size, TileCoordinates, Unique } from "./types" | ||
import { ImageSchema, SizeSchema } from './schemas' | ||
import Jimp from 'jimp' | ||
import { SpliteaError, ThrowSpliteaError } from './errors' | ||
import type { CropData, Image, Natural, Size, StoreOptions, UniqueImagesOptions, WriteOptions } from './types' | ||
|
||
export const getSize = (image: Jimp): Size => v.parse(SizeSchema, { width: image.bitmap.width, height: image.bitmap.height}) | ||
|
||
export const getImage = async (image: Image): Promise<[Jimp, Size]> => { | ||
// @ts-expect-error | ||
export const getImage = async (image: Image): Promise<Jimp> => { | ||
try { | ||
const i = v.parse(ImageSchema, image) | ||
// @ts-ignore | ||
const img = await Jimp.read(i) | ||
const size: Size = getSize(img) | ||
return [img, size] | ||
// @ts-expect-error | ||
return await Jimp.read(image) | ||
} catch (error) { | ||
throw ThrowSpliteaError(error, `Error reading image ${image}`) | ||
ThrowSpliteaError(error, `Error reading image ${image.toString()}`) | ||
} | ||
} | ||
|
||
const getSplitImage = (image: Jimp, size: Size, tileCoordinates: TileCoordinates): Jimp => { | ||
try { | ||
const { width, height } = size | ||
const { x, y, width: w, height: h } = tileCoordinates | ||
if (x === 0 && w === width && y === 0 && h === height) return image | ||
if ((x + w) > width) throw new SpliteaError(`Can't have an image of ${w}x${h}px from (${x}, ${y}) because max x value is ${width - 1}`) | ||
if ((y + h) > height) throw new SpliteaError(`Can't have an image of ${w}x${h}px from (${x}, ${y}) because max y value is ${height - 1}`) | ||
return image.clone().crop(x, y, w, h) | ||
} catch (error) { | ||
throw ThrowSpliteaError(error, 'Problem spliting image') | ||
} | ||
} | ||
export const getSize = (image: Jimp): Size => ({ width: image.bitmap.width, height: image.bitmap.height }) | ||
|
||
export const areEqualImages = (img1: Jimp, img2: Jimp, unique: Unique): boolean => { | ||
const { requirement, distance, difference } = unique | ||
const areEqualImages = (img1: Jimp, img2: Jimp, options: UniqueImagesOptions): boolean => { | ||
const { requirement, distance, difference } = options | ||
try { | ||
// Distance | ||
if (requirement === 'distance') { return Jimp.distance(img1, img2) <= distance } | ||
// Difference | ||
if (requirement === 'difference') { return Jimp.diff(img1, img2).percent <= difference } | ||
// Distance + Difference | ||
const dist = Jimp.distance(img1, img2) | ||
const diff = Jimp.diff(img1, img2).percent | ||
return ( requirement === 'both') | ||
? (dist < distance && diff < difference) | ||
: (dist < distance || diff < difference) | ||
return (dist <= distance && diff <= difference) | ||
} catch (error) { | ||
console.log(error) | ||
ThrowSpliteaError(error, 'Error comparing images') | ||
} | ||
return false | ||
} | ||
|
||
export const getUniqueImages = (images: Jimp[], unique: Unique): Jimp[] => { | ||
const getUniqueTiles = (images: Jimp[], options: UniqueImagesOptions): Jimp[] => { | ||
if (images.length < 2) return images | ||
let array = [...images] | ||
let uniques: Jimp[] = [] | ||
const uniques: Jimp[] = [] | ||
do { | ||
const image: Jimp = array.shift() as Jimp | ||
uniques.push(image) | ||
array = array.filter(elem => !areEqualImages(image, elem, unique)) | ||
} while (array.length > 0) | ||
array = array.filter(elem => !areEqualImages(image, elem, options)) | ||
} while (array.length > 0) | ||
return uniques | ||
} | ||
|
||
export const getSplitImages = (image: Jimp, size: Size, tilesCoordinate: TileCoordinates[], unique: Unique | undefined): Jimp[] => { | ||
const images = tilesCoordinate.map(tileCoordinates => getSplitImage(image, size, tileCoordinates)) | ||
if (unique && images.length > 1) { return getUniqueImages(images, unique) } | ||
return images | ||
const getTile = (image: Jimp, { x, y, w, h }: CropData): Jimp => image.clone().crop(x, y, w, h) | ||
|
||
export const getHorizontalTiles = (image: Jimp, size: Size, width: Natural): Jimp[] => { | ||
if (size.width === width) return [image] | ||
const tiles = [] | ||
const y = 0 | ||
const w = width | ||
const h = size.height | ||
for (let x = 0; x < size.width; x += width) { | ||
try { | ||
tiles.push(getTile(image, { x, y, w, h })) | ||
} catch (error) { | ||
ThrowSpliteaError(error, 'Cannot get Horizontal tiles') | ||
} | ||
} | ||
return tiles | ||
} | ||
|
||
export const getUniqueHorizontalTiles = (image: Jimp, size: Size, width: Natural, options: UniqueImagesOptions): Jimp[] => { | ||
const tiles = getHorizontalTiles(image, size, width) | ||
return getUniqueTiles(tiles, options) | ||
} | ||
|
||
const writeImage = async (image: Jimp, path: string, name: string, index: number | string, extension: string): Promise<string> => { | ||
const filename = `${name}_${(index).toString().padStart(3, '0')}.${extension}` | ||
export const getVerticalTiles = (image: Jimp, size: Size, height: Natural): Jimp[] => { | ||
if (size.height === height) return [image] | ||
const tiles = [] | ||
const x = 0 | ||
const w = size.width | ||
const h = height | ||
for (let y = 0; y < size.height; y += height) { | ||
try { | ||
tiles.push(getTile(image, { x, y, w, h })) | ||
} catch (error) { | ||
ThrowSpliteaError(error, 'Cannot get Vertical tiles') | ||
} | ||
} | ||
return tiles | ||
} | ||
|
||
export const getUniqueVerticalTiles = (image: Jimp, size: Size, height: Natural, options: UniqueImagesOptions): Jimp[] => { | ||
const tiles = getVerticalTiles(image, size, height) | ||
return getUniqueTiles(tiles, options) | ||
} | ||
|
||
export const getGridTiles = (image: Jimp, size: Size, width: Natural, height: Natural): Jimp[] => { | ||
if (size.width === width) return [image] | ||
const tiles = [] | ||
const w = width | ||
const h = height | ||
for (let x = 0; x < size.width; x += width) { | ||
for (let y = 0; y < size.height; y += height) { | ||
try { | ||
tiles.push(getTile(image, { x, y, w, h })) | ||
} catch (error) { | ||
ThrowSpliteaError(error, 'Cannot get Grid tiles') | ||
} | ||
} | ||
} | ||
return tiles | ||
} | ||
|
||
export const getUniqueGridTiles = (image: Jimp, size: Size, width: Natural, height: Natural, options: UniqueImagesOptions): Jimp[] => { | ||
const tiles = getGridTiles(image, size, width, height) | ||
return getUniqueTiles(tiles, options) | ||
} | ||
|
||
const writeImage = async (image: Jimp, options: WriteOptions): Promise<string> => { | ||
const { path, filename: name, extension, index, pad } = options | ||
const filename = `${name}_${(index).toString().padStart(pad, '0')}.${extension}` | ||
const file = Path.join(path, filename) | ||
await image.writeAsync(file) | ||
return file | ||
} | ||
|
||
export const writeImages = async (images: Jimp[], path: string, name: string, extension: string): Promise<string[]> => { | ||
export const writeImages = async (images: Jimp[], storeOptions: StoreOptions): Promise<string[]> => { | ||
if (images.length < 1) throw new SpliteaError('Impossible to write no images') | ||
const { path, filename, extension } = storeOptions | ||
const pad = Math.floor(Math.log10(images.length)) + 1 | ||
if (images.length === 1) { | ||
const filenames = await writeImage(images[0], path, name, '', extension) | ||
const filenames = await writeImage(images[0], { path, filename, extension, index: '', pad }) | ||
return [filenames] | ||
} | ||
return Promise.all( | ||
return await Promise.all( | ||
images.map( | ||
async (image: Jimp, index: number) => await writeImage(image, path, name, index, extension) | ||
async (image: Jimp, index: number) => await writeImage(image, { path, filename, extension, index: index.toString(), pad }) | ||
) | ||
) | ||
} | ||
|
||
// @ts-expect-error | ||
export const getBufferImages = async (images: Jimp[]): Promise<Buffer[]> => { | ||
try { | ||
const buffers = await Promise.all(images.map(async (image: Jimp) => await image.getBufferAsync(image.getMIME()))) | ||
return buffers | ||
return await Promise.all( | ||
images.map(async (image: Jimp) => await image.getBufferAsync(image.getMIME())) | ||
) | ||
} catch (error) { | ||
ThrowSpliteaError(error, 'Impossible to get buffer from images') | ||
} | ||
return Promise.resolve([]) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
// phash with sharp -> https://www.npmjs.com/package/sharp-phash | ||
// phash -> https://www.npmjs.com/search?q=phash | ||
|
||
// import * as Path from 'node:path' | ||
// import Jimp from "jimp" | ||
// import sharp, { Sharp } from 'sharp' | ||
// import { SpliteaError, ThrowSpliteaError } from "./errors" | ||
// import { Image, ImageSchema, Size, SizeSchema, TileCoordinates } from "./types" | ||
|
||
// export const getSize = async (image: Sharp): Promise<Size> => { | ||
// const { width, height } = await image.metadata() | ||
// return SizeSchema.parse({ width, height }) | ||
// } | ||
|
||
// export const getImage = async (image: Image): Promise<[Sharp, Size]> => { | ||
// try { | ||
// ImageSchema.parse(image) | ||
// const img = sharp(image) | ||
// const { width, height } = await img.metadata() | ||
// const size: Size = SizeSchema.parse({ width, height }) | ||
// return [img, size] | ||
// } catch (error) { | ||
// throw ThrowSpliteaError(error, `Error reading image ${image}`) | ||
// } | ||
// } | ||
|
||
// const getSplitImage = (image: Sharp, size: Size, tileCoordinates: TileCoordinates): Sharp => { | ||
// try { | ||
// const { width, height } = size | ||
// const { x, y, width: w, height: h } = tileCoordinates | ||
// if (x === 0 && w === width && y === 0 && h === height) return image | ||
// if ((x + w) > width) throw new SpliteaError(`Can't have an image of ${w}x${h}px from (${x}, ${y}) because max x value is ${width - 1}`) | ||
// if ((y + h) > height) throw new SpliteaError(`Can't have an image of ${w}x${h}px from (${x}, ${y}) because max y value is ${height - 1}`) | ||
// return image.clone().extract({ left: x, top: y, width: w, height: h }) | ||
// } catch (error) { | ||
// throw ThrowSpliteaError(error, 'Problem spliting image') | ||
// } | ||
// } | ||
|
||
// export const getSplitImages = (image: Sharp, size: Size, tilesCoordinate: TileCoordinates[], unique: boolean = false): Sharp[] => { | ||
// const images = tilesCoordinate.map(tileCoordinates => getSplitImage(image, size, tileCoordinates)) | ||
// if (unique && images.length > 1) { return getUniqueImages(images) } | ||
// return images | ||
// } | ||
|
||
// export const areEqualImages = (img1: Sharp, img2: Sharp): boolean => { | ||
// try { | ||
// const distance = Jimp.distance(img1, img2) | ||
// const diff = Jimp.diff(img1, img2) | ||
// if (distance < 0.15 && diff.percent < 0.15) { | ||
// console.debug(`distance = ${distance} | diff = ${diff.percent}`) | ||
// return true | ||
// } | ||
// } catch (error) { | ||
// console.log(error) | ||
// ThrowSpliteaError(error, 'Error comparing images') | ||
// } | ||
// return false | ||
// } | ||
|
||
// export const getUniqueImages = (images: Sharp[]): Sharp[] => { | ||
// let array = [...images] | ||
// let image: Sharp | ||
// let uniqueArray: Sharp[] = [] | ||
// while (array.length) { | ||
// image = array[0] | ||
// uniqueArray.push(image) | ||
// array = array.filter(elem => !areEqualImages(image, elem)) | ||
// } | ||
// return uniqueArray | ||
// } | ||
|
||
// const writeImage = async (image: Jimp, path: string, name: string, index: number | string, extension: string): Promise<string> => { | ||
// const filename = `${name}_${index}_${new Date().getTime()}.${extension}` | ||
// const file = Path.join(path, filename) | ||
// await image.writeAsync(file) | ||
// return file | ||
// } | ||
|
||
// export const writeImages = async (images: Jimp[], path: string, name: string, extension: string): Promise<string[]> => { | ||
// if (images.length < 1) throw new SpliteaError('Impossible to write no images') | ||
// if (images.length === 1) { | ||
// const filenames = await writeImage(images[0], path, name, '', extension) | ||
// return [filenames] | ||
// } | ||
// return Promise.all( | ||
// images.map( | ||
// async (image: Jimp, index: number) => await writeImage(image, path, name, index, extension) | ||
// ) | ||
// ) | ||
// } | ||
|
||
// export const getBufferImages = async (images: Jimp[]): Promise<Buffer[]> => { | ||
// try { | ||
// const buffers = await Promise.all(images.map(async (image: Jimp) => await image.getBufferAsync(image.getMIME()))) | ||
// return buffers | ||
// } catch (error) { | ||
// ThrowSpliteaError(error, 'Impossible to get buffer from images') | ||
// } | ||
// return Promise.resolve([]) | ||
// } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
import Splitea from './splitea' | ||
|
||
export { Splitea } | ||
export * from './splitea' | ||
export * from './constants' | ||
export * from './types' |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.