image-blob-reduce
Version:
High quality image resize in browser for blobs (`pica` wrapper with some sugar)
148 lines (107 loc) • 4.05 kB
JavaScript
;
var image_traverse = require('./image_traverse');
function jpeg_patch_exif(env) {
return this._getUint8Array(env.blob).then(function (data) {
env.is_jpeg = image_traverse.is_jpeg(data);
if (!env.is_jpeg) return Promise.resolve(env);
env.orig_blob = env.blob;
try {
var exif_is_big_endian, orientation_offset;
/* eslint-disable consistent-return */
image_traverse.jpeg_exif_tags_each(data, function (entry) {
if (entry.ifd === 0 && entry.tag === 0x112 && Array.isArray(entry.value)) {
env.orientation = entry.value[0] || 1;
exif_is_big_endian = entry.is_big_endian;
orientation_offset = entry.data_offset;
return false;
}
});
if (orientation_offset) {
var orientation_patch = exif_is_big_endian ?
new Uint8Array([ 0, 1 ]) :
new Uint8Array([ 1, 0 ]);
env.blob = new Blob([
data.slice(0, orientation_offset),
orientation_patch,
data.slice(orientation_offset + 2)
], { type: 'image/jpeg' });
}
} catch (_) {}
return env;
});
}
function jpeg_rotate_canvas(env) {
if (!env.is_jpeg) return Promise.resolve(env);
var orientation = env.orientation - 1;
if (!orientation) return Promise.resolve(env);
var canvas;
if (orientation & 4) {
canvas = this.pica.options.createCanvas(env.out_canvas.height, env.out_canvas.width);
} else {
canvas = this.pica.options.createCanvas(env.out_canvas.width, env.out_canvas.height);
}
var ctx = canvas.getContext('2d');
ctx.save();
if (orientation & 1) ctx.transform(-1, 0, 0, 1, canvas.width, 0);
if (orientation & 2) ctx.transform(-1, 0, 0, -1, canvas.width, canvas.height);
if (orientation & 4) ctx.transform(0, 1, 1, 0, 0, 0);
ctx.drawImage(env.out_canvas, 0, 0);
ctx.restore();
// Safari 12 workaround
// https://github.com/nodeca/pica/issues/199
env.out_canvas.width = env.out_canvas.height = 0;
env.out_canvas = canvas;
return Promise.resolve(env);
}
function jpeg_attach_orig_segments(env) {
if (!env.is_jpeg) return Promise.resolve(env);
return Promise.all([
this._getUint8Array(env.blob),
this._getUint8Array(env.out_blob)
]).then(function (res) {
var data = res[0];
var data_out = res[1];
if (!image_traverse.is_jpeg(data)) return Promise.resolve(env);
var segments = [];
image_traverse.jpeg_segments_each(data, function (segment) {
if (segment.code === 0xDA /* SOS */) return false;
segments.push(segment);
});
segments = segments
.filter(function (segment) {
// Drop ICC_PROFILE
//
if (segment.code === 0xE2) return false;
// Keep all APPn segments excluding APP2 (ICC_PROFILE),
// remove others because most of them depend on image data (DCT and such).
//
// APP0 - JFIF, APP1 - Exif, the rest are photoshop metadata and such
//
// See full list at https://www.w3.org/Graphics/JPEG/itu-t81.pdf (table B.1 on page 32)
//
if (segment.code >= 0xE0 && segment.code < 0xF0) return true;
// Keep comments
//
if (segment.code === 0xFE) return true;
return false;
})
.map(function (segment) {
return data.slice(segment.offset, segment.offset + segment.length);
});
env.out_blob = new Blob(
// intentionally omitting expected JFIF segment (offset 2 to 20)
[ data_out.slice(0, 2) ].concat(segments).concat([ data_out.slice(20) ]),
{ type: 'image/jpeg' }
);
return env;
});
}
function assign(reducer) {
reducer.before('_blob_to_image', jpeg_patch_exif);
reducer.after('_transform', jpeg_rotate_canvas);
reducer.after('_create_blob', jpeg_attach_orig_segments);
}
module.exports.jpeg_patch_exif = jpeg_patch_exif;
module.exports.jpeg_rotate_canvas = jpeg_rotate_canvas;
module.exports.jpeg_attach_orig_segments = jpeg_attach_orig_segments;
module.exports.assign = assign;