Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
314
gaia/apps/gallery/js/open.js
Executable file
314
gaia/apps/gallery/js/open.js
Executable file
|
|
@ -0,0 +1,314 @@
|
|||
'use strict';
|
||||
/* global
|
||||
CONFIG_MAX_IMAGE_PIXEL_SIZE,
|
||||
CONFIG_REQUIRED_EXIF_PREVIEW_HEIGHT,
|
||||
CONFIG_REQUIRED_EXIF_PREVIEW_WIDTH,
|
||||
Downsample,
|
||||
GestureDetector,
|
||||
getImageSize,
|
||||
getStorageIfAvailable,
|
||||
getUnusedFilename,
|
||||
MediaFrame,
|
||||
MimeMapper,
|
||||
NFC,
|
||||
parseJPEGMetadata
|
||||
*/
|
||||
|
||||
(function init() {
|
||||
var activity; // The activity object we're handling
|
||||
var activityData; // The data sent by the initiating app
|
||||
var blob; // The blob we'll be displaying and maybe saving
|
||||
var frame; // The MediaFrame that displays the image
|
||||
var saved = false; // Did we save the file?
|
||||
var storage; // The DeviceStorage object used for saving
|
||||
var title; // What we call the image in the titlebar and banner
|
||||
|
||||
// Register a handler to receive the Activity object
|
||||
navigator.mozSetMessageHandler('activity', handleOpenActivity);
|
||||
|
||||
function $(id) { return document.getElementById(id); }
|
||||
|
||||
// Display the specified blob, unless it is too big to display
|
||||
function open(blob) {
|
||||
|
||||
// If the app that initiated this activity wants us to do allow the
|
||||
// user to save this blob as a file, and if device storage is available
|
||||
// and if there is enough free space, then display a save button.
|
||||
if (activityData.allowSave && activityData.filename && checkFilename()) {
|
||||
getStorageIfAvailable('pictures', blob.size, function(ds) {
|
||||
storage = ds;
|
||||
showSaveButton();
|
||||
});
|
||||
}
|
||||
|
||||
// Figure out how big (in pixels) the image is.
|
||||
// For JPEG images, this also gets us the preview image if there is one.
|
||||
getImageSize(blob, success, error);
|
||||
|
||||
// Called if we get an image size
|
||||
function success(metadata) {
|
||||
var pixels = metadata.width * metadata.height;
|
||||
|
||||
//
|
||||
// If the image is too big, reject it now so we don't have
|
||||
// memory trouble later.
|
||||
//
|
||||
// CONFIG_MAX_IMAGE_PIXEL_SIZE is maximum image resolution we
|
||||
// can handle. It's from config.js which is generated at build
|
||||
// time (see build/application-data.js).
|
||||
//
|
||||
// For jpeg images, we can downsample while decoding so we can
|
||||
// handle images that are quite a bit larger
|
||||
//
|
||||
var imagesizelimit = CONFIG_MAX_IMAGE_PIXEL_SIZE;
|
||||
if (blob.type === 'image/jpeg') {
|
||||
imagesizelimit *= Downsample.MAX_AREA_REDUCTION;
|
||||
}
|
||||
|
||||
//
|
||||
// Even if we can downsample an image while decoding it, we still
|
||||
// have to read the entire image file. If the file is particularly
|
||||
// large we might also have memory problems. (See bug 1008834: a 20mb
|
||||
// 80mp jpeg file will cause an OOM on Tarako even though we can
|
||||
// decode it at < 2mp). Rather than adding another build-time config
|
||||
// variable to specify the maximum file size, however, we'll just
|
||||
// base the file size limit on CONFIG_MAX_IMAGE_PIXEL_SIZE.
|
||||
// So if that variable is set to 2M, then we might use up to 12Mb of
|
||||
// memory. 2 * 2M bytes for the image file and 4 bytes times 2M pixels
|
||||
// for the decoded image. A 4mb file size limit should accomodate
|
||||
// most JPEG files up to 12 or 16 megapixels
|
||||
//
|
||||
var filesizelimit = 2 * CONFIG_MAX_IMAGE_PIXEL_SIZE;
|
||||
|
||||
if (pixels > imagesizelimit || blob.size > filesizelimit) {
|
||||
displayError('imagetoobig');
|
||||
return;
|
||||
}
|
||||
|
||||
// If there was no EXIF preview, or if the image is not very big,
|
||||
// display the full-size image.
|
||||
if (!metadata.preview || pixels < 512 * 1024) {
|
||||
frame.displayImage(blob,
|
||||
metadata.width,
|
||||
metadata.height,
|
||||
null,
|
||||
metadata.rotation,
|
||||
metadata.mirrored);
|
||||
}
|
||||
else {
|
||||
// If we found an EXIF preview, and can determine its size, then
|
||||
// we can display it instead of the big image and save a lot of
|
||||
// memory.
|
||||
parseJPEGMetadata(blob.slice(metadata.preview.start,
|
||||
metadata.preview.end,
|
||||
'image/jpeg'),
|
||||
function success(previewmetadata) {
|
||||
//
|
||||
// If we parsed the preview image, add its
|
||||
// dimensions to the metdata.preview
|
||||
// object, and then let the MediaFrame
|
||||
// object display the preview instead of
|
||||
// the full-size image.
|
||||
//
|
||||
metadata.preview.width = previewmetadata.width;
|
||||
metadata.preview.height = previewmetadata.height;
|
||||
frame.displayImage(blob,
|
||||
metadata.width,
|
||||
metadata.height,
|
||||
metadata.preview,
|
||||
metadata.rotation,
|
||||
metadata.mirrored);
|
||||
},
|
||||
function error() {
|
||||
//
|
||||
// If we couldn't parse the preview image,
|
||||
// just display full-size.
|
||||
//
|
||||
frame.displayImage(blob,
|
||||
metadata.width,
|
||||
metadata.height,
|
||||
null,
|
||||
metadata.rotation,
|
||||
metadata.mirrored);
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Called if getImageSize parsing fails.
|
||||
function error(msg) {
|
||||
//
|
||||
// This wasn't a JPEG, PNG, GIF, or BMP image and we can't figure
|
||||
// out the size of the image. Without the size, we can't pass the
|
||||
// image to MediaFrame.displayImage().
|
||||
//
|
||||
// XXX: Currently, we know how to get the sizes of all the image
|
||||
// types that this activity is registered to handle. If we add new
|
||||
// image types to manifest.webapp, then we should update
|
||||
// shared/js/media/image_size.js to compute the size or we should
|
||||
// add code here to load the image into an offscreen <img> to
|
||||
// determine its size (but only if the file size is not too large).
|
||||
//
|
||||
displayError('imageinvalid');
|
||||
}
|
||||
}
|
||||
|
||||
function handleOpenActivity(request) {
|
||||
activity = request;
|
||||
activityData = activity.source.data;
|
||||
|
||||
// Set up the UI, if it is not already set up
|
||||
if (!frame) {
|
||||
|
||||
// Hook up the buttons
|
||||
$('header').addEventListener('action', done);
|
||||
$('save').addEventListener('click', save);
|
||||
|
||||
// And register event handlers for gestures
|
||||
frame = new MediaFrame($('frame'), false, CONFIG_MAX_IMAGE_PIXEL_SIZE);
|
||||
|
||||
if (CONFIG_REQUIRED_EXIF_PREVIEW_WIDTH) {
|
||||
frame.setMinimumPreviewSize(CONFIG_REQUIRED_EXIF_PREVIEW_WIDTH,
|
||||
CONFIG_REQUIRED_EXIF_PREVIEW_HEIGHT);
|
||||
}
|
||||
|
||||
var gestureDetector = new GestureDetector(frame.container);
|
||||
gestureDetector.startDetecting();
|
||||
frame.container.addEventListener('dbltap', handleDoubleTap);
|
||||
frame.container.addEventListener('transform', handleTransform);
|
||||
frame.container.addEventListener('pan', handlePan);
|
||||
frame.container.addEventListener('swipe', handleSwipe);
|
||||
|
||||
window.addEventListener('resize', frame.resize.bind(frame));
|
||||
if (activityData.exitWhenHidden) {
|
||||
window.addEventListener('visibilitychange', function() {
|
||||
if (document.hidden) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Report errors if we're passed an invalid image
|
||||
frame.onerror = function invalid() {
|
||||
displayError('imageinvalid');
|
||||
};
|
||||
}
|
||||
|
||||
// Display the filename in the header, if there was one
|
||||
title = baseName(activityData.filename || '');
|
||||
$('filename').textContent = title;
|
||||
|
||||
blob = activityData.blob;
|
||||
open(blob);
|
||||
NFC.share(blob, {
|
||||
foregroundElement: $('open'),
|
||||
backgroundElement: document.body
|
||||
});
|
||||
}
|
||||
|
||||
function checkFilename() {
|
||||
// Hide save button for file names having hidden
|
||||
// .gallery/ directories. See Bug 992426
|
||||
if (activityData.filename.indexOf('.gallery/') != -1) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
var dotIdx = activityData.filename.lastIndexOf('.');
|
||||
if (dotIdx > -1) {
|
||||
var ext = activityData.filename.substr(dotIdx + 1);
|
||||
return MimeMapper.guessTypeFromExtension(ext) === blob.type;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function displayError(msgid) {
|
||||
document.l10n.formatValue(msgid).then(function(val) {
|
||||
alert(val);
|
||||
done();
|
||||
});
|
||||
}
|
||||
|
||||
function done() {
|
||||
activity.postResult({ saved: saved });
|
||||
activity = null;
|
||||
NFC.unshare();
|
||||
}
|
||||
|
||||
function handleDoubleTap(e) {
|
||||
var scale;
|
||||
if (frame.fit.scale > frame.fit.baseScale) {
|
||||
scale = frame.fit.baseScale / frame.fit.scale;
|
||||
} else {
|
||||
scale = 2;
|
||||
}
|
||||
|
||||
frame.zoom(scale, e.detail.clientX, e.detail.clientY, 200);
|
||||
}
|
||||
|
||||
function handleTransform(e) {
|
||||
frame.zoom(e.detail.relative.scale,
|
||||
e.detail.midpoint.clientX,
|
||||
e.detail.midpoint.clientY);
|
||||
}
|
||||
|
||||
function handlePan(e) {
|
||||
frame.pan(e.detail.relative.dx, e.detail.relative.dy);
|
||||
}
|
||||
|
||||
function handleSwipe(e) {
|
||||
var direction = e.detail.direction;
|
||||
var velocity = e.detail.vy;
|
||||
if (direction === 'down' && velocity > 2) {
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
function save() {
|
||||
|
||||
// Hides the save button: we can only save once
|
||||
hideSaveButton();
|
||||
|
||||
getUnusedFilename(storage, activityData.filename, function(filename) {
|
||||
var savereq = storage.addNamed(blob, filename);
|
||||
savereq.onsuccess = function() {
|
||||
// Remember that it has been saved so we can pass this back
|
||||
// to the invoking app
|
||||
saved = filename;
|
||||
// And tell the user
|
||||
showBanner('saved', title);
|
||||
};
|
||||
savereq.onerror = function(e) {
|
||||
// XXX we don't report this to the user because it is hard to
|
||||
// localize.
|
||||
console.error('Error saving', filename, e);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function showSaveButton() {
|
||||
$('save').classList.remove('hidden');
|
||||
// XXX work around bug 870619
|
||||
$('filename').textContent = $('filename').textContent;
|
||||
}
|
||||
|
||||
function hideSaveButton() {
|
||||
$('save').classList.add('hidden');
|
||||
// XXX work around bug 870619
|
||||
$('filename').textContent = $('filename').textContent;
|
||||
}
|
||||
|
||||
function showBanner(msg, title) {
|
||||
document.l10n.setAttributes($('message'), msg, {filename: title});
|
||||
$('banner').hidden = false;
|
||||
setTimeout(function() {
|
||||
$('banner').hidden = true;
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// Strip directories and just return the base filename
|
||||
function baseName(filename) {
|
||||
return filename.substring(filename.lastIndexOf('/') + 1);
|
||||
}
|
||||
})();
|
||||
Loading…
Reference in a new issue