Skip to content

Commit

Permalink
fix(image-shrink): improve icc profile support (#902)
Browse files Browse the repository at this point in the history
* fix(image-shrink): improve icc profile support

* fix: add marker start check & refactor `getIccProfile`

* chore: remove console.log

* chore: rewrite `getExif` and `getIccProfile` with arrow funcs

* chore: prevent code execution after skipping reject

* chore: use `indexOf` instead of `includes` to fix IE support

* chore: more arrow funcs & simplify resolve inside catch

* chore: make `stripIccProfile` always return image of original or modified file

* fix: prevent code execution after df reject & fix double image loading
  • Loading branch information
nd0ut committed Apr 4, 2022
1 parent 98807d1 commit 806734e
Showing 1 changed file with 140 additions and 49 deletions.
189 changes: 140 additions & 49 deletions src/utils/image-processor.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,79 +12,78 @@ var DataView = isWindowDefined() && window.DataView
var runner = taskRunner(1)

const shrinkFile = function (file, settings) {
var df
// in -> file
// out <- blob
df = $.Deferred()
const df = $.Deferred()
if (!(URL && DataView && Blob)) {
return df.reject('support')
}
// start = new Date()
runner((release) => {
var op
// console.log('delayed: ' + (new Date() - start))
df.always(release)
// start = new Date()
op = imageLoader(URL.createObjectURL(file))
op.always(function (img) {
return URL.revokeObjectURL(img.src)
})
op.fail(function () {
return df.reject('not image')
})

return op.done(function (img) {
const op = shouldSkipShrink(file)
.then((shouldSkip) => {
if (shouldSkip) {
df.reject('skipped')
return $.Deferred().reject()
}
})
.then(() =>
stripIccProfile(file).fail(() => {
df.reject('not image')
})
)

op.done((img) => {
// console.log('load: ' + (new Date() - start))
df.notify(0.1)

var exifOp = $.when(getExif(file), isBrowserApplyExif()).always(function (
exif,
isExifApplied
) {
var e, isJPEG
const exifOp = $.when(
getExif(file),
isBrowserApplyExif(),
getIccProfile(file)
).always((exif, isExifApplied, iccProfile) => {
df.notify(0.2)
isJPEG = exifOp.state() === 'resolved'
const isJPEG = exifOp.state() === 'resolved'
// start = new Date()
op = shrinkImage(img, settings)
op.progress(function (progress) {
const op = shrinkImage(img, settings)
op.progress((progress) => {
return df.notify(0.2 + progress * 0.6)
})
op.fail(df.reject)
op.done(function (canvas) {
var format, quality
op.done((canvas) => {
// console.log('shrink: ' + (new Date() - start))
// start = new Date()
format = 'image/jpeg'
quality = settings.quality || 0.8
let format = 'image/jpeg'
let quality = settings.quality || 0.8
if (!isJPEG && hasTransparency(canvas)) {
format = 'image/png'
quality = undefined
}
return canvasToBlob(canvas, format, quality, function (blob) {
canvasToBlob(canvas, format, quality, (blob) => {
canvas.width = canvas.height = 1
df.notify(0.9)
// console.log('to blob: ' + (new Date() - start))
let replaceChain = $.Deferred().resolve(blob)
if (exif) {
if (isExifApplied) {
setExifOrientation(exif, 1)
}
op = replaceJpegChunk(blob, 0xe1, [exif.buffer])
op.done(df.resolve)

return op.fail(function () {
return df.resolve(blob)
})
} else {
return df.resolve(blob)
replaceChain = replaceChain
.then((blob) => replaceExif(blob, exif, isExifApplied))
.catch(() => blob)
}
if (iccProfile?.length > 0) {
replaceChain = replaceChain
.then((blob) => replaceIccProfile(blob, iccProfile))
.catch(() => blob)
}

replaceChain.done(df.resolve)
replaceChain.fail(() => df.resolve(blob))
})
})
e = null // free reference

return e
})

return exifOp
})
})

Expand Down Expand Up @@ -274,29 +273,121 @@ const replaceJpegChunk = function (blob, marker, chunks) {
}

const getExif = function (file) {
var exif, op
exif = null
op = readJpegChunks(file)
let exif = null
const op = readJpegChunks(file)
op.progress(function (pos, l, marker, view) {
if (!exif && marker === 0xe1) {
if (view.byteLength >= 14) {
if (view.getUint32(0) === 0x45786966 && view.getUint16(4) === 0) {
if (
// check for "Exif\0"
view.getUint32(0) === 0x45786966 &&
view.getUint16(4) === 0
) {
exif = view
return exif
}
}
}
})
return op.then(
function () {
return exif
},
function (reason) {
return $.Deferred().reject(exif, reason)
() => exif,
() => $.Deferred().reject(exif)
)
}

const getIccProfile = function (file) {
const iccProfile = []
const op = readJpegChunks(file)
op.progress(function (pos, l, marker, view) {
if (marker === 0xe2) {
if (
// check for "ICC_PROFILE\0"
view.getUint32(0) === 0x4943435f &&
view.getUint32(4) === 0x50524f46 &&
view.getUint32(8) === 0x494c4500
) {
iccProfile.push(view)
}
}
})
return op.then(
() => iccProfile,
() => $.Deferred().reject(iccProfile)
)
}

const replaceExif = function (blob, exif, isExifApplied) {
if (isExifApplied) {
setExifOrientation(exif, 1)
}
return replaceJpegChunk(blob, 0xe1, [exif.buffer])
}

const replaceIccProfile = function (blob, iccProfile) {
return replaceJpegChunk(
blob,
0xe2,
iccProfile.map((chunk) => chunk.buffer)
)
}

const stripIccProfile = function (inputFile) {
const df = $.Deferred()

replaceIccProfile(inputFile, [])
.catch(() => inputFile)
.then((file) => {
const op = imageLoader(URL.createObjectURL(file))
op.always((img) => {
URL.revokeObjectURL(img.src)
})
op.fail(() => {
df.reject()
})
op.done((img) => {
df.resolve(img)
})
})
.fail(() => {
df.reject()
})

return df.promise()
}

const shouldSkipShrink = (file) => {
const allowLayers = [
1, // L (black-white)
3 // RGB
]
const markers = [
0xc0, // ("SOF0", "Baseline DCT", SOF)
0xc1, // ("SOF1", "Extended Sequential DCT", SOF)
0xc2, // ("SOF2", "Progressive DCT", SOF)
0xc3, // ("SOF3", "Spatial lossless", SOF)
0xc5, // ("SOF5", "Differential sequential DCT", SOF)
0xc6, // ("SOF6", "Differential progressive DCT", SOF)
0xc7, // ("SOF7", "Differential spatial", SOF)
0xc9, // ("SOF9", "Extended sequential DCT (AC)", SOF)
0xca, // ("SOF10", "Progressive DCT (AC)", SOF)
0xcb, // ("SOF11", "Spatial lossless DCT (AC)", SOF)
0xcd, // ("SOF13", "Differential sequential DCT (AC)", SOF)
0xce, // ("SOF14", "Differential progressive DCT (AC)", SOF)
0xcf // ("SOF15", "Differential spatial (AC)", SOF)
]
let skip = false
const op = readJpegChunks(file)
op.progress(function (pos, l, marker, view) {
if (!skip && markers.indexOf(marker) >= 0) {
const layer = view.getUint8(5)
if (allowLayers.indexOf(layer) < 0) {
skip = true
}
}
})
return op.then(() => skip).catch(() => skip)
}

const setExifOrientation = function (exif, orientation) {
findExifOrientation(exif, (offset, little) =>
exif.setUint16(offset, orientation, little)
Expand Down

0 comments on commit 806734e

Please sign in to comment.