UNPKG

4.54 kBJavaScriptView Raw
1const exec = require('child_process').exec
2const fs = require('fs-extra')
3const jpegjs = require('jpeg-js')
4const path = require('path')
5const piexif = require('piexifjs')
6const pixelmatch = require('pixelmatch')
7const PNG = require('pngjs').PNG
8
9module.exports = {
10 checkTransformation,
11 transformWithCli,
12}
13
14/**
15 * Compare the transformed buffer with the original one,
16 * and return the result to the spec file to be tested
17 */
18function checkTransformation(originPathOrBuffer, transformedBuffer, orientation, dimensions) {
19 const origBuffer = typeof originPathOrBuffer === 'string' ? fs.readFileSync(originPathOrBuffer) : originPathOrBuffer
20 const origExif = piexif.load(origBuffer.toString('binary'))
21 const origJpeg = jpegjs.decode(origBuffer)
22 if (typeof originPathOrBuffer === 'string') {
23 fs.writeFileSync(originPathOrBuffer.replace('samples/', '.tmp/'), transformedBuffer)
24 }
25 return {
26 dimensionsMatch: compareDimensions(origJpeg, orientation, dimensions),
27 exifMatch: compareExif(origExif, piexif.load(transformedBuffer.toString('binary'))),
28 pixelsMatch: typeof originPathOrBuffer === 'string' ? comparePixels(originPathOrBuffer, transformedBuffer) : true,
29 }
30}
31
32/**
33 * Compare origin and destination pixels with pixelmatch, and save the diff on disk
34 */
35function comparePixels(originPathOrBuffer, transformedBuffer) {
36 const targetBuffer = fs.readFileSync(originPathOrBuffer.replace('.jpg', '_dest.jpg'))
37 const targetJpeg = jpegjs.decode(targetBuffer)
38 const diffPng = new PNG({width: targetJpeg.width, height: targetJpeg.height})
39 const diffPixels = pixelmatch(
40 jpegjs.decode(transformedBuffer).data,
41 targetJpeg.data,
42 diffPng.data,
43 targetJpeg.width,
44 targetJpeg.height,
45 {
46 threshold: 0.25,
47 }
48 )
49 const diffPath = path.join(
50 path.join(__dirname, '.tmp'),
51 path.parse(originPathOrBuffer).base.replace('.jpg', '.diff.png')
52 )
53 diffPng.pack().pipe(fs.createWriteStream(diffPath))
54 return diffPixels === 0
55}
56
57/**
58 * Compare origin and destination dimensions
59 * Depending on the original orientation, they may be switched
60 */
61function compareDimensions(origJpeg, orientation, dimensions) {
62 if (orientation < 5 && (origJpeg.width !== dimensions.width || origJpeg.height !== dimensions.height)) {
63 return false
64 }
65 if (orientation >= 5 && (origJpeg.width !== dimensions.height || origJpeg.height !== dimensions.width)) {
66 return false
67 }
68 return true
69}
70
71/**
72 * Compare EXIF arrays
73 * The properties allowed to differ between origin and destination images are set to 0
74 */
75function compareExif(orig, dest) {
76 orig['thumbnail'] = 0 // The thumbnail
77 dest['thumbnail'] = 0
78 orig['0th'][piexif.ImageIFD.Orientation] = 0 // Orientation
79 dest['0th'][piexif.ImageIFD.Orientation] = 0
80 orig['0th'][piexif.ImageIFD.ExifTag] = 0 // Pointer to the Exif IFD
81 dest['0th'][piexif.ImageIFD.ExifTag] = 0
82 orig['Exif'][piexif.ExifIFD.PixelXDimension] = 0 // Image width
83 dest['Exif'][piexif.ExifIFD.PixelXDimension] = 0
84 orig['Exif'][piexif.ExifIFD.PixelYDimension] = 0 // Image height
85 dest['Exif'][piexif.ExifIFD.PixelYDimension] = 0
86 orig['1st'][piexif.ImageIFD.JPEGInterchangeFormat] = 0 // Offset to the start byte of the thumbnail
87 dest['1st'][piexif.ImageIFD.JPEGInterchangeFormat] = 0
88 orig['1st'][piexif.ImageIFD.JPEGInterchangeFormatLength] = 0 // Number of bytes of the thumbnail
89 dest['1st'][piexif.ImageIFD.JPEGInterchangeFormatLength] = 0
90 return JSON.stringify(orig) === JSON.stringify(dest)
91}
92
93/**
94 * Transform the given path using the CLI module,
95 * and return the transformation data by parsing stdout
96 */
97function transformWithCli(originPath, quality) {
98 return new Promise((resolve, reject) => {
99 const destPath = originPath.replace('.jpg', '_cli.jpg')
100 const command = ['cp ' + originPath + ' ' + destPath, './src/cli.js ' + destPath + ' --quality=' + quality]
101 exec(command.join(' && '), function(error, stdout) {
102 if (error) {
103 return reject(error)
104 }
105 const output = stdout.match(
106 /Processed \(Orientation: ([0-9]{1})\) \(Quality: ([0-9]+)%\) \(Dimensions: ([0-9]+)x([0-9]+)\)/
107 )
108 fs.readFile(destPath)
109 .then((buffer) => {
110 return fs.remove(destPath).then(() => {
111 resolve({
112 buffer,
113 orientation: parseInt(output[1]),
114 quality: parseInt(output[2]),
115 dimensions: {width: parseInt(output[3]), height: parseInt(output[4])},
116 })
117 })
118 })
119 .catch(reject)
120 })
121 })
122}