Skip to content

Commit

Permalink
built images support
Browse files Browse the repository at this point in the history
  • Loading branch information
cancerberoSgx committed Nov 15, 2018
1 parent 7be7a38 commit 957a27b
Show file tree
Hide file tree
Showing 15 changed files with 186 additions and 77 deletions.
8 changes: 4 additions & 4 deletions samples/interactive-execute-context/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,10 @@ export class App extends React.Component<AppProps, AppState> {
this.setState({ ...this.state, commandString: e.target.value, commandArray })
}

async componentDidMount() {
const builtInImages = ['rose:', 'logo:', 'wizard:', 'granite:', 'netscape:']
const files = await pmap(builtInImages, i=>)
}
// async componentDidMount() {
// const builtInImages = ['rose:', 'logo:', 'wizard:', 'granite:', 'netscape:']
// const files = await pmap(builtInImages, i=>)
// }
protected async execute() {
const { outputFiles } = await this.props.context.execute(this.state.commandString)
this.state.outputFiles = outputFiles
Expand Down
10 changes: 10 additions & 0 deletions spec/executionContextSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,14 @@ export default describe('executionContext', () => {
expect(all1.map(f => f.name)).toEqual(all2.map(f => f.name))
done()
})

it('should addBuiltInImages()', async done =>{
let all = await context.getAllFiles()
expect(all.find(i=>i.name==='rose:')).toBeUndefined()
const builtIn = await context.addBuiltInImages()
expect(builtIn.find(i=>i.name==='rose:')).toBeDefined()
all = await context.getAllFiles()
expect(all.find(i=>i.name==='rose:')).toBeDefined()
done()
})
})
5 changes: 4 additions & 1 deletion spec/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ export * from './formatSpec'
export * from './imageHomeSpec'
export * from './executionContextSpec'
export * from './testAssetsSpec'
// export * from './util/index'
export * from './util/cliSpec'
export * from './util/htmlSpec'
export * from './util/fileSpec'
export * from './util/imageSpec'
export * from './util/imageBuiltInSpec'
export * from './util/imageCompareSpec'
export * from './util/imageExtractInfoSpec'

26 changes: 26 additions & 0 deletions spec/util/imageBuiltInSpec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { getBuiltInImages, builtInImageNames, extractInfo, execute, compare } from '../../src';
import pMap from 'p-map';

export default describe('util/imageBuiltIn', () => {

it('should get all builtIn images', async done => {
const all = await getBuiltInImages()
builtInImageNames.forEach(name => {
expect(all.map(f => f.name)).toContain(name, 'does not contain image ' + name)
})
done()
})

it('should be equal and info should match', async done => {
const all = await getBuiltInImages()
const formats = { 'rose:': 'PPM', 'logo:': 'GIF', 'wizard:': 'GIF', 'granite:': 'GIF', 'netscape:': 'GIF' }
await pMap(all, async img => {
const info = await extractInfo(img)
expect(info[0].image.format).toBe(formats[img.name])
const { outputFiles } = await execute({ commands: `convert ${img.name} 'output.png` })
expect(await compare(outputFiles[0], img)).toBe(true)
}, { concurrency: 1 })
done()
})

})
34 changes: 34 additions & 0 deletions spec/util/imageCompareSpec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { buildInputFile, Call, compare, MagickInputFile, outputFileToInputFile } from '../../src';

export default describe('util/imageCompare', () => {

async function test(img1: MagickInputFile | string, img2: MagickInputFile | string, expectedResult: boolean) {
const result = await compare(img1, img2)
expect(result).toBe(expectedResult, `Expected compareImage(${typeof img1 === 'string' ? img1 : img1.name} and ${typeof img2 === 'string' ? img2 : img2.name}) to return ${expectedResult} but returned ${result}`)
}

it('should return true if image is the same', async done => {
await test(await buildInputFile('fn.png', 'img1.png'), await buildInputFile('fn.png', 'img2.png'), true)
done()
})

it('should return false if images are different', async done => {
await test(await buildInputFile('fn.png', 'img1.png'), await buildInputFile('holocaust.jpg', 'img2.png'), false)
done()
})

it('should return true if images are equal but different formats', async done => {
const result = await Call([await buildInputFile('fn.png')], ['convert', 'fn.png', 'fn.jpg'])
const img1 = await outputFileToInputFile(result[0])
const img2 = await buildInputFile('fn.png', 'img2.png')
await test(img1, img2, true)
done()
})

it('should let me work with builtin images', async done => {
await test('rose:', await outputFileToInputFile((await Call([], ['convert', 'rose:', 'fn.png']))[0]), true)
await test('rose:', await outputFileToInputFile((await Call([], ['convert', 'wizard:', 'fn.png']))[0]), false)
done()
})

})
2 changes: 1 addition & 1 deletion spec/util/imageExtractInfoSpec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default describe('util/imageExtractInfo', () => {
done()
})

fit('should extract info from built-in image rose:', async done => {
it('should extract info from built-in images', async done => {
const info = await extractInfo('rose:')
expect(info[0].image.geometry.width).toBe(70)
expect(info[0].image.geometry.height).toBe(46)
Expand Down
31 changes: 0 additions & 31 deletions spec/util/imageSpec.ts

This file was deleted.

30 changes: 19 additions & 11 deletions src/execute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import pMap from 'p-map'
export type Command = (string | number)[]

export interface ExecuteConfig {
inputFiles: MagickInputFile[]
inputFiles?: MagickInputFile[]
/** commands could be array form like [['convert', 'foo.png', 'bar.gif']] or CLI form like ['convert foo.png bar.gif'] */
commands: ExecuteCommand
}
Expand All @@ -19,6 +19,7 @@ export interface ExecuteResult {
/** execute first command in given config */
export async function executeOne(config: ExecuteConfig): Promise<ExecuteResult> {
try {
config.inputFiles = config.inputFiles || []
const command = asCommand(config.commands)[0]
const t0 = performance.now()
executeListeners.forEach(listener => listener.beforeExecute({ command, took: performance.now() - t0, id: t0 }))
Expand Down Expand Up @@ -63,20 +64,27 @@ export function addExecuteListener(l: ExecuteListener) {
* })
* ```
*
* Alternatively it support CLI like command line instead of arrays:
*
* An alternative syntax with CLI-like strings is also supported:
*
* ```ts
* const {outputFiles} = await execute({
* inputFiles: [await buildInputFile('fn.png', 'image1.png')],
* commands: [
* 'convert image1.png -rotate 70 image2.gif',
* // heads up: next command uses 'image2.gif' which was the output of previous command:
* 'convert image2.gif -scale 23% image3.jpg',
* ]
* })
* const {outputFiles} = await execute({inputFiles: [], commands: [
* 'convert rose: -rotate 70 image2.gif',
* 'convert image2.gif -resize 33 image3.gif'
* ] })
* ```
*
* Or if it's only one command using just a string:
*
* ```ts
* const {outputFiles} = await execute({inputFiles: [foo], commands: `convert 'my face image.png' \\( +clone -channel R -fx B \\) +swap -channel B -fx v.R bar.gif`})
* ```
*
* Note: in string syntax you must use single quotes for CLI arguments that need so (like 'my face image.png'). no multiline with \ is supported.
*
* ```
*/
export async function execute(config: ExecuteConfig): Promise<ExecuteResult> {
config.inputFiles = config.inputFiles || []
const allOutputFiles: { [name: string]: MagickOutputFile } = {}
const allInputFiles: { [name: string]: MagickInputFile } = {}
config.inputFiles.forEach(f => {
Expand Down
44 changes: 34 additions & 10 deletions src/executionContext.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ImageHome, ExecuteConfig, ExecuteResult, execute, ExecuteCommand, createImageHome, MagickInputFile, MagickFile, extractInfo } from '.'
import pMap from 'p-map';
import { asOutputFile, asInputFile, getBuiltInImages } from './util';

/**
* Allow multiple execute() calls remembering previus execute() generated output files and previous given input files that can be used as input files in next calls.
Expand All @@ -8,15 +9,18 @@ export interface ExecutionContext {
execute(configOrCommands: ExecuteConfig|ExecuteCommand|string): Promise<ExecuteResult>
addFiles(files: MagickFile[]): void
getAllFiles(): Promise<MagickInputFile[]>
addBuiltInImages(): Promise<MagickFile[]>
getFile(name: string): Promise<MagickInputFile>
}

export function newExecutionContext(inheritFrom?: ExecutionContext): ExecutionContext {
return ExecutionContextImpl.create(inheritFrom)
}

class ExecutionContextImpl implements ExecutionContext {

private builtInImages: MagickInputFile[] = []

constructor(private imageHome: ImageHome = createImageHome()) {
}

async execute(configOrCommands: ExecuteConfig|ExecuteCommand|string): Promise<ExecuteResult> {
const config = asConfig(configOrCommands)
config.inputFiles.forEach(f => {
Expand All @@ -29,20 +33,36 @@ class ExecutionContextImpl implements ExecutionContext {
})
return result
}

addFiles(files: MagickFile[]) {
files.forEach(f => this.imageHome.register(f))
}

async getAllFiles(): Promise<MagickInputFile[]> {
return this.imageHome.getAll()
const all= await this.imageHome.getAll()
return all.concat(this.builtInImages)
}

async getFile(name: string): Promise<MagickInputFile> {
return (await this.imageHome.get(name)) || this.builtInImages.find(i=>i.name===name)
}


async addBuiltInImages() {
const builtInImages = ['rose:', 'logo:', 'wizard:', 'granite:', 'netscape:']

pMap(builtInImages, async name=>{
const info = extractInfo(name)
// const {outputFiles} =
})
if(!this.builtInImages.length){
this.builtInImages = await getBuiltInImages()
// const builtInImages = ['rose:', 'logo:', 'wizard:', 'granite:', 'netscape:']
// this.builtInImages = await pMap(builtInImages, async name=>{
// const info = await extractInfo(name)
// // const outputName = `output1.${info[0].image.format.toLowerCase()}`
// const {outputFiles} = await execute({commands:`convert ${name} ${`output1.${info[0].image.format.toLowerCase()}`}`} )
// outputFiles[0].name = name
// return await asInputFile(outputFiles[0])
// })
}
return this.builtInImages
}

static create(inheritFrom?: ExecutionContext) {
if (inheritFrom && !(inheritFrom as ExecutionContextImpl).imageHome) {
throw new Error('Dont know how to inherith from other ExecutionContext implementation than this one')
Expand All @@ -59,3 +79,7 @@ function asConfig(configOrCommands: ExecuteConfig|ExecuteCommand|string): Execut
return (configOrCommands as ExecuteConfig).inputFiles ? (configOrCommands as ExecuteConfig) :
({commands: configOrCommands as ExecuteCommand, inputFiles: ([] as MagickInputFile[])} as ExecuteConfig)
}

export function newExecutionContext(inheritFrom?: ExecutionContext): ExecutionContext {
return ExecutionContextImpl.create(inheritFrom)
}
4 changes: 2 additions & 2 deletions src/util/html.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { MagickFile, MagickInputFile } from '../magickApi'
import { asOutputFile, buildInputFile } from './file'
import { MagickFile, MagickInputFile } from '../magickApi';
import { asOutputFile } from './file';

// utilities related to HTML (img) elements

Expand Down
15 changes: 0 additions & 15 deletions src/util/image.ts

This file was deleted.

17 changes: 17 additions & 0 deletions src/util/imageBuiltIn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import pMap from "p-map";
import { MagickInputFile, extractInfo , execute, asInputFile} from "..";

let builtInImages: MagickInputFile[]
export const builtInImageNames = ['rose:', 'logo:', 'wizard:', 'granite:', 'netscape:']

export async function getBuiltInImages(): Promise<MagickInputFile[]> {
if(!builtInImages){
builtInImages = await pMap(builtInImageNames, async name=>{
const info = await extractInfo(name)
const {outputFiles} = await execute({commands:`convert ${name} ${`output1.${info[0].image.format.toLowerCase()}`}`} )
outputFiles[0].name = name
return await asInputFile(outputFiles[0])
})
}
return builtInImages
}
32 changes: 32 additions & 0 deletions src/util/imageCompare.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { asInputFile, Call, MagickFile, blobToString, MagickInputFile } from '..'

export async function compare(img1: MagickFile | string, img2: MagickFile | string, error: number = 0.01): Promise<boolean> {
const identical = await compareNumber(img1, img2)
return identical <= error
}

export async function compareNumber(img1: MagickFile | string, img2: MagickFile | string): Promise<number> {
let name1: string, imgs: MagickInputFile[] = [], name2: string
if (typeof img1 !== 'string') {
const inputFile = await asInputFile(img1)
imgs.push(inputFile)
name1 = inputFile.name
}
else {
name1 = img1
}
if (typeof img2 !== 'string') {
const inputFile = await asInputFile(img2)
imgs.push(inputFile)
name2 = inputFile.name
}
else {
name2 = img2
}
const result = await Call(
imgs,
['convert', name1, name2, '-resize', '256x256^!', '-metric', 'RMSE', '-format', '%[distortion]', '-compare', 'info:info.txt'],
)
const n = await blobToString(result[0].blob)
return parseFloat(n)
}
2 changes: 1 addition & 1 deletion src/util/imageExtractInfo.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { asInputFile, Call, MagickFile, blobToString, MagickInputFile } from '..'

/**
* Execute convert $IMG info.json to extract image metadata. Returns the parsed info.json file contents
* Execute `convert $IMG info.json` to extract image metadata. Returns the parsed info.json file contents
*
* TODO: support several input images - we are already returning an array
* @param img could be a string in case you want to extract information about built in images like `rose:`
Expand Down
3 changes: 2 additions & 1 deletion src/util/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './file'
export * from './cli'
export * from './html'
export * from './image'
export * from './imageBuiltIn'
export * from './imageCompare'
export * from './imageExtractInfo'
export * from './support'

0 comments on commit 957a27b

Please sign in to comment.