1 | const exec = require('child_process').exec
|
2 | const fs = require('fs-extra')
|
3 | const jpegjs = require('jpeg-js')
|
4 | const path = require('path')
|
5 | const piexif = require('piexifjs')
|
6 | const pixelmatch = require('pixelmatch')
|
7 | const PNG = require('pngjs').PNG
|
8 |
|
9 | module.exports = {
|
10 | checkTransformation,
|
11 | transformWithCli,
|
12 | }
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 | function 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 |
|
34 |
|
35 | function 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 |
|
59 |
|
60 |
|
61 | function 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 |
|
73 |
|
74 |
|
75 | function compareExif(orig, dest) {
|
76 | orig['thumbnail'] = 0
|
77 | dest['thumbnail'] = 0
|
78 | orig['0th'][piexif.ImageIFD.Orientation] = 0
|
79 | dest['0th'][piexif.ImageIFD.Orientation] = 0
|
80 | orig['0th'][piexif.ImageIFD.ExifTag] = 0
|
81 | dest['0th'][piexif.ImageIFD.ExifTag] = 0
|
82 | orig['Exif'][piexif.ExifIFD.PixelXDimension] = 0
|
83 | dest['Exif'][piexif.ExifIFD.PixelXDimension] = 0
|
84 | orig['Exif'][piexif.ExifIFD.PixelYDimension] = 0
|
85 | dest['Exif'][piexif.ExifIFD.PixelYDimension] = 0
|
86 | orig['1st'][piexif.ImageIFD.JPEGInterchangeFormat] = 0
|
87 | dest['1st'][piexif.ImageIFD.JPEGInterchangeFormat] = 0
|
88 | orig['1st'][piexif.ImageIFD.JPEGInterchangeFormatLength] = 0
|
89 | dest['1st'][piexif.ImageIFD.JPEGInterchangeFormatLength] = 0
|
90 | return JSON.stringify(orig) === JSON.stringify(dest)
|
91 | }
|
92 |
|
93 |
|
94 |
|
95 |
|
96 |
|
97 | function 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 | }
|