Skip to content

Commit

Permalink
All code refactored to use new API and hardening of Validators
Browse files Browse the repository at this point in the history
  • Loading branch information
crisconru committed Jun 28, 2024
1 parent 7055f3b commit 995b28d
Show file tree
Hide file tree
Showing 20 changed files with 946 additions and 1,050 deletions.
8 changes: 6 additions & 2 deletions src/constants.ts
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
14 changes: 7 additions & 7 deletions src/errors.ts
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))
}
148 changes: 99 additions & 49 deletions src/image.ts
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([])
}
}
101 changes: 101 additions & 0 deletions src/imageSharp.ts
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([])
// }
6 changes: 3 additions & 3 deletions src/index.ts
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'
26 changes: 0 additions & 26 deletions src/output.ts

This file was deleted.

Loading

0 comments on commit 995b28d

Please sign in to comment.