VulpesOS/gaia/apps/gallery/js/open.js
OverSu b38dea0597 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.
2026-09-30 23:13:03 +02:00

314 lines
11 KiB
JavaScript
Executable file

'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);
}
})();