Skip to content
This repository has been archived by the owner on Nov 3, 2021. It is now read-only.

Commit

Permalink
Merge pull request #33519 from hfiguiere/bug1178812-exif-copy
Browse files Browse the repository at this point in the history
Bug 1178812 - Add metadata copying to crop and resize. r=djf
  • Loading branch information
hfiguiere committed Jan 8, 2016
2 parents a46654e + ccfbfbc commit d4212d8
Show file tree
Hide file tree
Showing 10 changed files with 3,485 additions and 26 deletions.
1 change: 1 addition & 0 deletions .jshintignore
Expand Up @@ -24,6 +24,7 @@ apps/clock/js/ext/**
apps/clock/js/text_builder.js
shared/js/uuid.js
shared/js/intl/l20n*.js
shared/js/media/jpeg-exif.js
apps/keyboard/js/imes/jspinyin/libpinyin.js
apps/keyboard/js/imes/jszhuyin/lib/**
apps/keyboard/js/imes/handwriting/hwr/**
Expand Down
1 change: 1 addition & 0 deletions apps/gallery/index.html
Expand Up @@ -53,6 +53,7 @@

<!-- This script is lazy loaded, but listed here so it gets packaged -->
<!-- <script defer src="shared/js/media/crop_resize_rotate.js"></script> -->
<!-- <script defer src="shared/js/media/jpeg-exif.js"></script> -->
<!-- <script defer src="shared/elements/gaia-header/dist/gaia-header.js"></script> -->

<!-- Web Components -->
Expand Down
80 changes: 69 additions & 11 deletions apps/gallery/js/ImageEditor.js
Expand Up @@ -19,7 +19,9 @@
photodb,
setView,
showFile,
Spinner
Spinner,
LazyLoader,
JPEGParser
*/
/* exported
editPhotoIfCardNotFull
Expand Down Expand Up @@ -298,6 +300,14 @@ function editPhoto(n) {
var imagesize = metadata.width * metadata.height;
var maxsize = CONFIG_MAX_EDIT_PIXEL_SIZE || CONFIG_MAX_IMAGE_PIXEL_SIZE;

if (file.type === 'image/jpeg') {
LazyLoader.load(['shared/js/media/jpeg-exif.js'], () => {
JPEGParser.readExifMetaData(file, (error, metaData) => {
editSettings.EXIF = metaData || {};
});
});
}

if (metadata.rotation || metadata.mirrored || imagesize > maxsize) {
Spinner.show();
cropResizeRotate(file, null, maxsize || null,
Expand Down Expand Up @@ -1189,8 +1199,8 @@ ImageEditor.prototype.getFullSizeBlob = function(type, done, progress) {
// Update the progress bar
processed_pixels += rect.w * rect.h;
if (progress) {
// Processing the pixels takes 90% of our time (as a rough guess)
progress(0.05 + 0.9 * processed_pixels / total_pixels);
// Processing the pixels takes 85% of our time (as a rough guess)
progress(0.05 + 0.85 * processed_pixels / total_pixels);
}
});
promises.push(promise);
Expand All @@ -1204,13 +1214,7 @@ ImageEditor.prototype.getFullSizeBlob = function(type, done, progress) {
// than they might otherwise be garbage collected.
processors.forEach(function(processor) { processor.destroy(); });

// Finally, convert the editd image to a blob and pass to the callback.
canvas.toBlob(function(blob) {
// Now that we've got the blob, we don't need the canvas anymore
context = null;
canvas.width = canvas.height = 0;
canvas = null;

function gotEncodedBlob(blob) {
// Move the progress bar to the end
if (progress) {
progress(1.0);
Expand All @@ -1221,8 +1225,62 @@ ImageEditor.prototype.getFullSizeBlob = function(type, done, progress) {
setTimeout(function() {
done(blob);
});
}

// we need to save these for Exif rewrite.
var canvasW = canvas.width;
var canvasH = canvas.height;

function updateExif(blob, metaData, callback) {
metaData.Orientation = 1;
if (metaData.PixelXDimension) {
metaData.PixelXDimension = canvasW;
}
if (metaData.PixelYDimension) {
metaData.PixelYDimension = canvasH;
}
JPEGParser.writeExifMetaData(
blob, metaData,
(error, modifiedBlob) => {
if (error) {
console.error('Error' + error);
}
// Process modified file
callback(modifiedBlob);
});
}

var originalBlob = this.imageBlob;
var originalExif = this.edits.EXIF;

// Finally, convert the edited image to a blob and pass to the callback.
canvas.toBlob(function(blob) {
// Now that we've got the blob, we don't need the canvas anymore
context = null;
canvas.width = canvas.height = 0;
canvas = null;
progress(0.95);

if (type == 'image/jpeg') {
// we are most likely to have originalExif here.
if (originalExif) {
updateExif(blob, originalExif, gotEncodedBlob);
} else {
LazyLoader.load(['shared/js/media/jpeg-exif.js'], () => {
JPEGParser.readExifMetaData(originalBlob, (error, metaData) => {
if (error || !metaData) {
gotEncodedBlob(blob);
return;
}
updateExif(blob, metaData, gotEncodedBlob);
});
});
}
} else {
gotEncodedBlob(blob);
}
}, type);
});
}.bind(this));
};

ImageEditor.prototype.isCropOverlayShown = function() {
Expand Down
11 changes: 9 additions & 2 deletions apps/gallery/js/pick.js
Expand Up @@ -220,12 +220,14 @@ var Pick = (function() {
// then we use null as the type. This value is passed to
// cropResizeRotate() and will leave the image unchanged if possible
// or will use jpeg if changes are needed.
// EXIF should be preserved.

if (Array.isArray(pickType)) {
if (pickType.indexOf(pickedFileInfo.type) !== -1) {
pickType = pickedFileInfo.type;
}
else if (pickType.indexOf('image/jpeg') !== -1) {
pickType = 'image/jpeg';
pickType = 'image/jpeg+exif';
}
else if (pickType.indexOf('image/png') !== -1) {
pickType = 'image/png';
Expand All @@ -238,9 +240,14 @@ var Pick = (function() {
pickType = null; // Return unchanged or convert to JPEG
}

if (pickType && pickType !== 'image/jpeg' && pickType !== 'image/png') {
if (pickType && pickType !== 'image/jpeg' &&
pickType !== 'image/jpeg+exif' && pickType !== 'image/png') {
pickType = null; // Return unchanged or convert to JPEG
}
else if (!pickType && pickedFileInfo.type == 'image/jpeg') {
// if we picked a JPEG image, then explicitly force EXIF copy.
pickType = 'image/jpeg+exif';
}

// In order to determine the cropRegion and outputSize arguments to
// cropResizeRotate() below we need to know the actual image size.
Expand Down
1 change: 1 addition & 0 deletions apps/gallery/test/unit/pick_test.js
Expand Up @@ -7,6 +7,7 @@ require('/shared/test/unit/mocks/mock_lazy_loader.js');
require('/shared/test/unit/mocks/mock_mediadb.js');
require('/shared/test/unit/mocks/mock_crop_resize_rotate.js');
require('/shared/test/unit/mocks/mock_navigator_getdevicestorage.js');
require('/shared/js/media/jpeg-exif.js');
requireApp('/gallery/test/unit/mock_image_editor.js');
requireApp('/gallery/test/unit/mock_spinner.js');
requireApp('/gallery/js/pick.js');
Expand Down
Binary file added apps/sharedtest/test-data/blue.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added apps/sharedtest/test-data/red.jpg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
124 changes: 122 additions & 2 deletions apps/sharedtest/test/unit/crop_resize_rotate_test.js
Expand Up @@ -4,20 +4,137 @@
/* global parseJPEGMetadata */
/* global getImageSize */
/* global Downsample */
/* global cropResizeRotate */
/* global cropResizeRotate, JPEGParser */

require('/shared/test/unit/mocks/mock_lazy_loader.js');
require('/shared/js/blobview.js');
require('/shared/js/media/jpeg_metadata_parser.js');
require('/shared/js/media/image_size.js');
require('/shared/js/media/downsample.js');
require('/shared/js/media/crop_resize_rotate.js');
require('/shared/js/media/jpeg-exif.js');

function fetchBlob(url) {
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = function() {
if (xhr.status !== 200) {
reject(new Error('Failed with status: ' + xhr.status));
} else {
resolve(this.response);
}
};
xhr.onerror = xhr.ontimeout = function() {
reject(new Error('Failed' + xhr.status));
};
xhr.responseType = 'blob';
xhr.send();
});
}

suite('Metadata copy test', function() {
suiteSetup(function() {

// We need the mock lazy loader for jpeg-exif.
window.LazyLoader = window.MockLazyLoader;
});


[
// this one is horizontal
{ fileName: '/test-data/blue.jpg', rotated: false,
targetSize: { width: 640, height: 480 },
date: '2015:07:16 09:42:26', make: 'QCOM-AA' },
{ fileName: '/test-data/blue.jpg', rotated: false,
targetSize: null,
date: '2015:07:16 09:42:26', make: 'QCOM-AA' },
{ fileName: '/test-data/blue.jpg', rotated: false,
targetCrop: { left: 10, top: 10, width: 150, height: 100 },
targetSize: null,
date: '2015:07:16 09:42:26', make: 'QCOM-AA' },
// this one is vertical (Exif orientation)
{ fileName: '/test-data/red.jpg', rotated: true,
targetSize: { width: 480, height: 640 },
date: '2015:07:13 21:16:38', make: 'Sony' },
{ fileName: '/test-data/red.jpg', rotated: true,
targetSize: null,
date: '2015:07:13 21:16:38', make: 'Sony' },
{ fileName: '/test-data/red.jpg', rotated: true,
targetCrop: { left: 10, top: 10, width: 100, height: 150 },
targetSize: null,
date: '2015:07:13 21:16:38', make: 'Sony' }
].
forEach(function (testFile) {
var testName = 'Testing metadata copy for ' + testFile.fileName;
if (testFile.targetSize) {
testName += ' resized to ' + testFile.targetSize.width +
'x' + testFile.targetSize.height;
}
test(testName, function(done) {

fetchBlob(testFile.fileName).then(function (blob) {
assert.ok(blob);

var targetSize = testFile.targetSize;
var targetCrop = testFile.targetCrop === undefined ? null :
testFile.targetCrop;
cropResizeRotate(
blob, targetCrop, targetSize, 'image/jpeg+exif',
function (error, outBlob) {
assert.equal(error, null);
assert.ok(outBlob);

JPEGParser.readExifMetaData(
outBlob, function (error, metaData) {
assert.ok(metaData);
// check metaData
assert.equal(metaData.Orientation, 1);
assert.equal(metaData.DateTimeOriginal, testFile.date);
assert.equal(metaData.Make, testFile.make);

var expectedX, expectedY;
if (targetSize) {
expectedX = targetSize.width;
expectedY = targetSize.height;
} else if (targetCrop) {
expectedX = targetCrop.width;
expectedY = targetCrop.height;
} else {
if (!testFile.rotated) {
expectedX = 1280;
expectedY = 960;
} else {
expectedX = 960;
expectedY = 1280;
}
}

assert.equal(metaData.PixelXDimension, expectedX);
assert.equal(metaData.PixelYDimension, expectedY);

done();
});
});
}).catch(function (e) {
assert(false, 'Caught error ' + e);
done(e);
});
});
});

});

function runCropResizeRotateTests(imageWidth, imageHeight) {
var suitename = 'cropResizeRotate tests ' + imageWidth + 'x' + imageHeight;
suite(suitename, function() {
const W = imageWidth, H = imageHeight; // The size of the test image

suiteSetup(function(done) {

// We need the mock lazy loader for jpeg-exif.
window.LazyLoader = window.MockLazyLoader;

// We begin by creating a special image where each pixel value
// encodes the coordinates of that pixel. This allows us to inspect
// the pixels in the output image to verify that cropping and rotation
Expand Down Expand Up @@ -103,11 +220,14 @@ function runCropResizeRotateTests(imageWidth, imageHeight) {
}
}

test('prerequsite modules loaded', function() {
test('prerequisite modules loaded', function() {
assert.isDefined(BlobView);
assert.typeOf(parseJPEGMetadata, 'function');
assert.typeOf(getImageSize, 'function');
assert.typeOf(cropResizeRotate, 'function');
assert.ok(window.MockLazyLoader);
assert.ok(window.LazyLoader);
assert.isDefined(JPEGParser);
});

test('test image created successfully', function(done) {
Expand Down

0 comments on commit d4212d8

Please sign in to comment.