Skip to content

Instantly share code, notes, and snippets.

@johnjohndoe
Last active July 28, 2021 18:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save johnjohndoe/0cfee6811b0a9de90c6e56b65fe60cb4 to your computer and use it in GitHub Desktop.
Save johnjohndoe/0cfee6811b0a9de90c6e56b65fe60cb4 to your computer and use it in GitHub Desktop.
h4 {
text-transform: uppercase;
}
.device-list {
padding: 1em 0 0 0;
margin: 0;
}
.device-list li {
display: inline-block;
vertical-align: bottom;
margin: 0;
margin-right: 20px;
text-align: center;
}
.device-list li .thumb-container {
display: inline-block;
}
.device-list li .thumb-container img {
margin-bottom: 8px;
opacity: 0.6;
-webkit-transition: -webkit-transform 0.2s, opacity 0.2s;
-moz-transition: -moz-transform 0.2s, opacity 0.2s;
transition: transform 0.2s, opacity 0.2s;
}
.device-list li.drag-hover .thumb-container img {
opacity: 1;
-webkit-transform: scale(1.1);
-moz-transform: scale(1.1);
transform: scale(1.1);
}
.device-list li .device-details {
font-size: 13px;
line-height: 16px;
color: #888;
}
.device-list li .device-url {
font-weight: bold;
}
#archive-expando {
display: block;
font-size: 13px;
font-weight: bold;
color: #333;
text-transform: uppercase;
margin-top: 16px;
padding-top: 16px;
padding-left: 28px;
border-top: 1px solid transparent;
background: transparent url(/images/styles/disclosure_down.png)
no-repeat scroll 0 8px;
}
#archive-expando.expanded {
background-image: url(/images/styles/disclosure_up.png);
border-top: 1px solid #ccc;
}
.device-list.archive {
max-height: 0;
overflow: hidden;
opacity: 0;
}
.device-list.archive.expanded {
opacity: 1;
max-height: 800px;
}
#output {
color: #f44;
font-style: italic;
position: relative;
}
#output img {
max-height: 500px;
}
.device-art-generator-messages {
display: none;
}
devsite.framebox.AutoSizeClient.updateSize();
// Global variables.
var g_currentImage;
var g_currentDevice;
var g_currentObjectURL;
var g_currentBlob;
// Global constants.
var MSG_INVALID_INPUT_IMAGE = 'invalidInputImg';
var MSG_INVALID_WEAR_IMAGE = 'invalidWearImg';
var MSG_NO_INPUT_IMAGE = 'noInputImg';
var MSG_GENERATING_IMAGE = 'generatingImage';
const PLEASE_USE_VALID = 'pleaseUseValid';
const SIZE_OUTPUT = 'sizeOutput';
const ASPECT_RATIO = 'aspectRatio';
const OR_MSG = 'or';
const IDEALLY = 'ideally';
var MAX_DISPLAY_HEIGHT = 126; // XOOM, to fit into 200px wide
// Get messages from markup:
const messageAttributes = {
[MSG_INVALID_INPUT_IMAGE]: 'msg-invalid-input-img',
[MSG_INVALID_WEAR_IMAGE]: 'msg-invalid-wear-img',
[MSG_NO_INPUT_IMAGE]: 'msg-no-input-img',
[MSG_GENERATING_IMAGE]: 'msg-generating-img',
[PLEASE_USE_VALID]: 'msg-use-valid-img',
[SIZE_OUTPUT]: 'msg-size-output',
[ASPECT_RATIO]: 'msg-aspect-ratio',
[OR_MSG]: 'msg-or',
[IDEALLY]: 'msg-ideally'
};
// Messages used for storing strings from the html markup.
let messages = {};
// Device manifest.
var DEVICES = [
{
archived: true,
density: '420DPI',
id: 'nexus_5x',
landOffset: [485,313],
landRes: ['shadow', 'back', 'fore'],
physicalHeight: 5.625,
physicalSize: 5.2,
portOffset: [305,485],
portRes: ['shadow', 'back', 'fore'],
portSize: [1080,1920],
title: 'Nexus 5X',
url: 'https://www.google.com/nexus/5x/',
},
{
density: '560DPI',
hidden: true,
id: 'nexus_6',
landOffset: [489,327],
landRes: ['shadow', 'back', 'fore'],
physicalHeight: 6.27,
physicalSize: 6,
portOffset: [327,489],
portRes: ['shadow', 'back', 'fore'],
portSize: [1440, 2560],
title: 'Nexus 6',
url: 'https://www.google.com/nexus/6/'
},
{
archived: true,
density: '560DPI',
id: 'nexus_6p',
landOffset: [579,321],
landRes: ['shadow', 'back', 'fore'],
physicalHeight: 6.125,
physicalSize: 5.7,
portOffset: [312,579],
portRes: ['shadow', 'back', 'fore'],
portSize: [1440, 2560],
title: 'Nexus 6P',
url: 'https://www.google.com/nexus/6p/',
},
{
actualResolution: [1536,2048],
archived: true,
density: '288DPI',
id: 'nexus_9',
landOffset: [514,350],
landRes: ['shadow', 'back', 'fore'],
physicalHeight: 8.98,
physicalSize: 9,
portOffset: [348,514],
portRes: ['shadow', 'back', 'fore'],
portSize: [1536,2048],
title: 'Nexus 9',
url: 'https://www.google.com/nexus/9/'
},
{
archived: true,
density: '441DPI',
id: 'pixel',
landOffset: [541,327],
landRes: ['shadow', 'back', 'fore'],
physicalHeight: 5.6,
physicalSize: 5,
portOffset: [315,541],
portRes: ['shadow', 'back', 'fore'],
portSize: [1080, 1920],
title: 'Pixel',
url: 'https://store.google.com/product/pixel_phone'
},
{
archived: true,
density: '534DPI',
id: 'pixel_xl',
landOffset: [581,348],
landRes: ['shadow', 'back', 'fore'],
physicalHeight: 6.0,
physicalSize: 5.5,
portOffset: [337,581],
portRes: ['shadow', 'back', 'fore'],
portSize: [1440, 2560],
title: 'Pixel XL',
url: 'https://store.google.com/product/pixel_phone'
},
{
archived: true,
density: '441DPI',
id: 'pixel_2',
landOffset: [350, 71],
landRes: ['shadow', 'back', 'fore'],
physicalHeight: 5.7,
physicalSize: 5,
portOffset: [139, 296],
portRes: ['shadow', 'back', 'fore'],
portSize: [1080, 1920],
title: 'Pixel 2',
url: 'https://store.google.com/product/pixel_2'
},
{
archived: true,
density: '538DPI',
id: 'pixel_2_xl',
landOffset: [301, 97],
landRes: ['shadow', 'back', 'fore'],
physicalHeight: 6.2,
physicalSize: 6,
portCornerRadius: 80,
portOffset: [202, 225],
portRes: ['shadow', 'back', 'fore'],
portSize: [1440, 2880],
title: 'Pixel 2 XL',
url: 'https://store.google.com/product/pixel_2'
},
{
density: 'HDPI',
hidden: true,
id: 'wear_square',
physicalHeight: 1.8,
physicalSize: 1.8,
landOffset: [225,206],
landRes: ['back'],
portOffset: [200,214],
portRes: ['back'],
portSize: [320,320],
title: 'Wear OS (square)',
url: 'https://www.android.com/wear/'
},
{
density: 'HDPI',
hidden: true,
id: 'wear_round',
landOffset: [161,167],
landRes: ['back'],
physicalHeight: 1.8,
physicalSize: 1.8,
portCornerRadius: 160,
portOffset: [128,134],
portRes: ['back'],
portSize: [320,320],
title: 'Wear OS (round)',
url: 'https://www.android.com/wear/'
},
{
archived: true,
density: '443DPI',
id: 'pixel_3',
landOffset: [226,98],
landRes: ['shadow', 'back', 'fore'],
physicalHeight: 5.7,
physicalSize: 5.5,
portOffset: [156,184],
portRes: ['shadow', 'back', 'fore'],
portSize: [1080, 2160],
title: 'Pixel 3',
url: 'https://store.google.com/product/pixel_3',
notch: true
},
{
archived: true,
density: '523DPI',
id: 'pixel_3_xl',
landOffset: [208,100],
landRes: ['shadow', 'back', 'fore'],
physicalHeight: 6.2,
physicalSize: 6.3,
portOffset: [216,216],
portRes: ['shadow', 'back', 'fore'],
portSize: [1440, 2960],
title: 'Pixel 3 XL',
url: 'https://store.google.com/product/pixel_3',
notch: true
},
{
archived: true,
density: '441DPI',
id: 'pixel_3a',
landOffset: [210,94],
landRes: ['shadow', 'back', 'fore'],
physicalHeight: 6,
physicalSize: 5.6,
portOffset: [160,160],
portRes: ['shadow', 'back', 'fore'],
portSize: [1080, 2220],
title: 'Pixel 3a',
url: 'https://store.google.com/product/pixel_3a',
notch: true
},
{
archived: true,
density: '402DPI',
id: 'pixel_3a_xl',
landOffset: [210,94],
landRes: ['shadow', 'back', 'fore'],
physicalHeight: 6.3,
physicalSize: 6,
portOffset: [160,160],
portRes: ['shadow', 'back', 'fore'],
portSize: [1080, 2160],
title: 'Pixel 3a XL',
url: 'https://store.google.com/product/pixel_3a',
notch: true
},
{
archived: true,
density: '444DPI',
id: 'pixel_4',
landOffset: [214,94],
landRes: ['shadow', 'back', 'fore'],
physicalHeight: 5.8,
physicalSize: 5.7,
portOffset: [132,160],
portRes: ['shadow', 'back', 'fore'],
portSize: [1080, 2280],
title: 'Pixel 4',
url: 'https://store.google.com/product/pixel_4',
notch: true
},
{
archived: true,
density: '537DPI',
id: 'pixel_4_xl',
landOffset: [252,100],
landRes: ['shadow', 'back', 'fore'],
physicalHeight: 6.3,
physicalSize: 6.3,
portOffset: [180,212],
portRes: ['shadow', 'back', 'fore'],
portSize: [1440, 3040],
title: 'Pixel 4 XL',
url: 'https://store.google.com/product/pixel_4',
notch: true
},
{
density: '433DPI',
id: 'pixel_4a',
landOffset: [140,96],
landRes: ['shadow', 'back', 'fore'],
physicalHeight: 6,
physicalSize: 6.2,
portOffset: [140,92],
portRes: ['shadow', 'back', 'fore'],
portSize: [1080, 2340],
title: 'Pixel 4a',
url: 'https://store.google.com/product/pixel_4a',
notch: true
},
{
density: '432DPI',
id: 'pixel_5',
landOffset: [142,84],
landRes: ['shadow', 'back', 'fore'],
physicalHeight: 5.7,
physicalSize: 6,
portOffset: [140,84],
portRes: ['shadow', 'back', 'fore'],
portSize: [1080, 2340],
title: 'Pixel 5',
url: 'https://store.google.com/product/pixel_5',
notch: true
},
{
archived: true,
density: '235DPI',
id: 'pixelbook',
landOffset: [519,200],
landRes: ['shadow', 'back', 'fore'],
physicalHeight: 8.7,
physicalSize: 12.3,
portOffset: [380,156],
portRes: ['shadow', 'back', 'fore'],
portSize: [1600, 2400],
title: 'Pixelbook',
url: 'https://store.google.com/product/google_pixelbook'
},
{
density: '166DPI',
id: 'pixelbook_go',
landOffset: [134,158],
landRes: ['shadow', 'back', 'fore'],
physicalHeight: 8.1,
physicalSize: 13.3,
portOffset: [158,128],
portRes: ['shadow', 'back', 'fore'],
portSize: [1080, 1920],
title: 'Pixelbook Go',
url: 'https://store.google.com/product/pixelbook_go',
notch: true
},
{
density: 'HDPI',
id: 'wear',
landOffset: [225,206],
landRes: ['back'],
physicalHeight: 1.8,
physicalSize: 1.8,
portOffset: [200,214],
portRes: ['back'],
portSize: [320,320],
title: 'Wear OS by Google',
url: 'https://www.android.com/wear/'
},
];
var MAX_HEIGHT = 0;
for (var i = 0; i < DEVICES.length; i++) {
MAX_HEIGHT = Math.max(MAX_HEIGHT, DEVICES[i].physicalHeight);
}
// Setup performed once the DOM is ready.
document.addEventListener('DOMContentLoaded', function() {
if (!checkBrowser()) {
return;
}
getTextContent();
polyfillCanvasToBlob();
setupUI();
// Set up Chrome drag-out
// $.event.props.push("dataTransfer");
document.body.addEventListener('dragstart', function(e) {
var target = e.target;
if (target.classList.contains('dragout')) {
e.dataTransfer.setData('DownloadURL', target.dataset.downloadurl);
}
}, false);
});
/**
* Grabs the text content from a ul that contains error messages that will
* be displayed to the user. The text and markup is put into a messages object
* that is referenced in this file.
*/
const getTextContent = () => {
Object.keys(messageAttributes).forEach((key, index) => {
messages[key] = document.querySelectorAll(`[${messageAttributes[key]}]`)[0].innerHTML;
});
};
/**
* Returns the device from DEVICES with the given id.
*/
function getDeviceById(id) {
for (var i = 0; i < DEVICES.length; i++) {
if (DEVICES[i].id == id)
return DEVICES[i];
}
return;
}
/**
* Checks to make sure the browser supports this page. If not,
* updates the UI accordingly and returns false.
*/
function checkBrowser() {
// Check for browser support
var browserSupportError = null;
// Must have <canvas>
var elem = document.createElement('canvas');
if (!elem.getContext || !elem.getContext('2d')) {
browserSupportError = 'HTML5 canvas.';
}
// Must have FileReader
if (!window.FileReader) {
browserSupportError = 'desktop file access';
}
if (browserSupportError) {
var supportedBrowserEl = document.querySelector('.supported-browser');
var unsupportedBrowserEl = document.querySelector('.unsupported-browser');
var unsupportedBrowserReasonEl =
document.querySelector('#unsupported-browser-reason');
supportedBrowserEl.style.display = 'none';
unsupportedBrowserReasonEl.innerHTML = browserSupportError;
unsupportedBrowserEl.style.display = 'block';
return false;
}
return true;
}
function setupUI() {
var outputEl = document.querySelector('#output');
outputEl.innerHTML = messages[MSG_NO_INPUT_IMAGE];
var frameCustomizationsEl = document.querySelector('#frame-customizations');
frameCustomizationsEl.style.display = 'none';
var wearCustomizationsEl = document.querySelector('#wear-customizations');
wearCustomizationsEl.style.display = 'none';
var outputShadowEl = document.querySelector('#output-shadow');
var outputGlareEl = document.querySelector('#output-glare');
outputShadowEl.addEventListener('click', createFrame);
outputGlareEl.addEventListener('click', createFrame);
var outputWearInputEl = document.querySelector('input[name="output-wear"]');
outputWearInputEl.addEventListener('change', createFrame);
// Build device list.
DEVICES.forEach(function(device) {
// Don't render device element if it's hidden.
if (device.hidden) {
return;
}
var resolution = device.actualResolution || device.portSize;
var scaleFactorText = '';
var deviceList = '.device-list.primary';
if (resolution[0] != device.portSize[0]) {
scaleFactorText = '<br>' +
(100 * (device.portSize[0] / resolution[0])).toFixed(0) +
'% ' + messages[SIZE_OUTPUT];
} else {
scaleFactorText = '<br>&nbsp;';
}
if (device.archived) {
deviceList = '.device-list.archive';
}
var deviceListEl = document.querySelector(deviceList);
var thumbEl = document.createElement('img');
thumbEl.src = 'device-art-resources/' + device.id + '/thumb.png';
thumbEl.style.height = Math.floor(MAX_DISPLAY_HEIGHT * device.physicalHeight / MAX_HEIGHT) + 'px';
var thumbContainerEl = document.createElement('div');
thumbContainerEl.classList.add('thumb-container');
thumbContainerEl.appendChild(thumbEl);
var deviceDetailsLinkEl = document.createElement('a');
deviceDetailsLinkEl.classList.add('device-url');
deviceDetailsLinkEl.href = device.url;
deviceDetailsLinkEl.target = '_blank';
deviceDetailsLinkEl.textContent = device.title;
var deviceDetailsEl = document.createElement('div');
deviceDetailsEl.classList.add('device-details');
deviceDetailsEl.appendChild(deviceDetailsLinkEl);
deviceDetailsEl.innerHTML += '<br>' + device.physicalSize + '" @ ' +
device.density + '<br>' + (resolution[0] + 'x' + resolution[1]) +
scaleFactorText;
var listEl = document.createElement('li');
listEl.dataset.deviceId = device.id;
listEl.appendChild(thumbContainerEl);
listEl.appendChild(deviceDetailsEl);
deviceListEl.appendChild(listEl);
});
// Set up "older devices" expando.
var archiveExpandoEl = document.querySelector('#archive-expando');
var deviceListArchiveEl = document.querySelector('.device-list.archive');
archiveExpandoEl.addEventListener('click', function() {
if (archiveExpandoEl.classList.contains('expanded')) {
archiveExpandoEl.classList.remove('expanded');
deviceListArchiveEl.classList.remove('expanded');
} else {
archiveExpandoEl.classList.add('expanded');
deviceListArchiveEl.classList.add('expanded');
}
devsite.framebox.AutoSizeClient.updateSize();
return false;
});
// Set up drag and drop.
var deviceListItemEls = document.querySelectorAll('.device-list li');
deviceListItemEls.forEach(function(deviceListItemEl) {
deviceListItemEl.addEventListener('dragover', function(e) {
deviceListItemEl.classList.add('drag-hover');
e.dataTransfer.dropEffect = 'link';
e.preventDefault();
});
deviceListItemEl.addEventListener('dragleave', function(e) {
deviceListItemEl.classList.remove('drag-hover');
});
deviceListItemEl.addEventListener('drop', function(e) {
var outputEl = document.querySelector('#output');
outputEl.innerHTML = messages[MSG_GENERATING_IMAGE];
deviceListItemEl.classList.remove('drag-hover');
g_currentDevice = getDeviceById(deviceListItemEl.dataset.deviceId);
e.preventDefault();
loadImageFromFileList(e.dataTransfer.files, function(data) {
if (data == null) {
if (g_currentDevice.id == 'wear') {
outputEl.innerHTML = messages[MSG_INVALID_WEAR_IMAGE];
}else {
outputEl.innerHTML = messages[MSG_INVALID_INPUT_IMAGE];
}
return;
}
loadImageFromUri(data.uri, function(img) {
g_currentFilename = data.name;
g_currentImage = img;
createFrame();
// Send the event to Analytics
if (devsite && devsite.analytics) {
devsite.analytics.trackAnalyticsEvent('Distribute', 'Create Device Art', g_currentDevice.title);
} else {
document.body.dispatchEvent(new CustomEvent('devsite-analytics-observation', {
detail: {'category': 'Distribute', 'action': 'Create Device Art', 'label': g_currentDevice.title },
bubbles: true,
}));
}
});
});
});
});
// Set up rotate button.
var rotateButtonEl = document.querySelector('#rotate-button');
rotateButtonEl.addEventListener('click', function() {
if (!g_currentImage) {
return;
}
var w = g_currentImage.naturalHeight;
var h = g_currentImage.naturalWidth;
var canvasEl = document.createElement('canvas');
canvasEl.width = w;
canvasEl.height = h;
var ctx = canvasEl.getContext('2d');
ctx.rotate(-Math.PI / 2);
ctx.translate(-h, 0);
ctx.drawImage(g_currentImage, 0, 0);
loadImageFromUri(canvasEl.toDataURL('image/png'), function(img) {
g_currentImage = img;
createFrame();
});
});
}
/**
* Generates the frame from the current selections (g_currentImage and g_currentDevice).
*/
function createFrame() {
var port;
var outputEl = document.querySelector('#output');
var outputShadowEl = document.querySelector('#output-shadow');
var outputGlareEl = document.querySelector('#output-glare');
if (g_currentDevice.id == 'wear' || g_currentDevice.id == 'wear_square' || g_currentDevice.id == 'wear_round') {
if (outputShadowEl.checked) {
g_currentDevice = getDeviceById('wear_square');
} else {
g_currentDevice = getDeviceById('wear_round');
}
}
var aspect1 = g_currentImage.naturalWidth / g_currentImage.naturalHeight;
var aspect2 = g_currentDevice.portSize[0] / g_currentDevice.portSize[1];
if (aspect1 == aspect2) {
port = true;
} else if (aspect1 == 1 / aspect2) {
port = false;
} else {
if (g_currentDevice.id == 'wear_square' || g_currentDevice.id == 'wear_round') {
alert(messages[ASPECT_RATIO] + ' ' + aspect2.toFixed(3) + ' (' +
messages[IDEALLY] + g_currentDevice.portSize[0] + ' x ' +
g_currentDevice.portSize[1] + ').');
outputEl.innerHTML = messages[MSG_INVALID_WEAR_IMAGE];
} else {
alert(messages[ASPECT_RATIO] + ' ' + aspect2.toFixed(3) + ' ' +
messages[OR_MSG] + ' ' + (1 / aspect2).toFixed(3) + ' (' +
messages[IDEALLY] + ' ' + g_currentDevice.portSize[0] + ' x ' +
g_currentDevice.portSize[1] + ').');
outputEl.innerHTML = messages[MSG_INVALID_INPUT_IMAGE];
}
return;
}
// Load image resources
var res = port ? g_currentDevice.portRes : g_currentDevice.landRes;
var resList = {};
for (var i = 0; i < res.length; i++) {
resList[res[i]] = 'device-art-resources/' + g_currentDevice.id + '/' +
(port ? 'port_' : 'land_') + res[i] + '.png'
}
var resourceImages = {};
loadImageResources(resList, function(r) {
resourceImages = r;
continueWithResources_();
});
function continueWithResources_() {
var width = resourceImages['back'].naturalWidth;
var height = resourceImages['back'].naturalHeight;
var offset = port ? g_currentDevice.portOffset : g_currentDevice.landOffset;
var size = port
? g_currentDevice.portSize
: [g_currentDevice.portSize[1], g_currentDevice.portSize[0]];
var radius = g_currentDevice.portCornerRadius || 0;
var x = 0;
var y = 0;
var canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext('2d');
if (resourceImages['shadow'] && outputShadowEl.checked) {
ctx.drawImage(resourceImages['shadow'], 0, 0);
}
ctx.drawImage(resourceImages['back'], 0, 0);
var scratchCanvas = document.createElement('canvas');
scratchCanvas.width = width;
scratchCanvas.height = height;
var scratchCtx = scratchCanvas.getContext('2d');
// draw screen: assume all are rounded rectangles
// (square corners have radius=0, circles have radius=width/2)
scratchCtx.clearRect(offset[0], offset[1],
scratchCanvas.width, scratchCanvas.height);
scratchCtx.globalCompositeOperation = 'source-over'; //default
scratchCtx.drawImage(g_currentImage, offset[0], offset[1],
size[0], size[1]);
scratchCtx.fillStyle = '#fff'; // full opacity
scratchCtx.globalCompositeOperation = 'destination-in';
scratchCtx.beginPath();
// top left corner
scratchCtx.arc(offset[0] + radius, offset[1] + radius,
radius, Math.PI, Math.PI * 1.5, false);
// top edge
scratchCtx.lineTo(offset[0] + size[0] - radius, offset[1]);
// top right corner
scratchCtx.arc(offset[0] + size[0] - radius, offset[1] + radius, radius,
Math.PI * 1.5, Math.PI * 2, false);
// right edge
scratchCtx.lineTo(offset[0] + size[0], offset[1] + size[1] - radius);
// bottom right corner
scratchCtx.arc(offset[0] + size[0] - radius, offset[1] + size[1] - radius,
radius, 0, Math.PI * .5, false);
// bottom edge
scratchCtx.lineTo(offset[0] + radius, offset[1] + size[1]);
// bottom left corner
scratchCtx.arc(offset[0] + radius, offset[1] + size[1] - radius, radius,
Math.PI * .5, Math.PI, false);
// left edge
scratchCtx.lineTo(offset[0], offset[1] + radius);
scratchCtx.closePath();
scratchCtx.fill();
// After tinkering with the offset, the 1 in the x-position drew the image
// perfectly
ctx.drawImage(scratchCanvas, 1, 0);
if (g_currentDevice.notch) {
ctx.drawImage(resourceImages['back'], 0, 0);
}
if (resourceImages['fore'] && outputGlareEl.checked) {
ctx.drawImage(resourceImages['fore'], 0, 0);
}
window.URL = window.URL || window.webkitURL;
if (canvas.toBlob && window.URL.createObjectURL) {
if (g_currentObjectURL) {
window.URL.revokeObjectURL(g_currentObjectURL);
g_currentObjectURL = null;
}
if (g_currentBlob) {
if (g_currentBlob.close) {
g_currentBlob.close();
}
g_currentBlob = null;
}
canvas.toBlob(function(blob) {
if (!blob) {
continueWithFinalUrl_(canvas.toDataURL('image/png'));
return;
}
g_currentBlob = blob;
g_currentObjectURL = window.URL.createObjectURL(blob);
continueWithFinalUrl_(g_currentObjectURL);
}, 'image/png');
} else {
continueWithFinalUrl_(canvas.toDataURL('image/png'));
}
}
function continueWithFinalUrl_(imageUrl) {
var filename = g_currentFilename
? g_currentFilename.replace(/^(.+?)(\.\w+)?$/, '$1_framed.png')
: 'framed_screenshot.png';
var linkImageEl = document.createElement('img');
linkImageEl.classList.add('dragout');
linkImageEl.src = imageUrl;
linkImageEl.draggable = true;
linkImageEl.dataset.downloadurl = ['image/png', filename, imageUrl].join(':');
var linkEl = document.createElement('a');
linkEl.download = filename;
linkEl.href = imageUrl;
linkEl.appendChild(linkImageEl);
outputEl.innerHTML = '';
outputEl.appendChild(linkEl);
var wearCustomizationsEl = document.querySelector('#wear-customizations');
var frameCustomizationsEl = document.querySelector('#frame-customizations');
if (g_currentDevice.id == 'wear' || g_currentDevice.id == 'wear_round' || g_currentDevice.id == 'wear_square') {
wearCustomizationsEl.style.display = 'block';
frameCustomizationsEl.style.display = 'none';
} else {
frameCustomizationsEl.style.display = 'block';
wearCustomizationsEl.style.display = 'none';
}
}
}
/**
* Loads an image from a data URI. The callback will be called with the <img> once
* it loads.
*/
function loadImageFromUri(uri, callback) {
callback = callback || function(){};
var img = document.createElement('img');
img.src = uri;
img.onload = function() {
callback(img);
};
img.onerror = function() {
callback(null);
}
}
/**
* Loads a set of images (organized by ID). Once all images are loaded, the callback
* is triggered with a dictionary of <img>'s, organized by ID.
*/
function loadImageResources(images, callback) {
var imageResources = {};
var checkForCompletion_ = function() {
for (var id in images) {
if (!(id in imageResources))
return;
}
(callback || function(){})(imageResources);
callback = null;
};
for (var id in images) {
var img = document.createElement('img');
img.src = images[id];
(function(img, id) {
img.onload = function() {
imageResources[id] = img;
checkForCompletion_();
};
img.onerror = function() {
imageResources[id] = null;
checkForCompletion_();
}
})(img, id);
}
}
/**
* Loads the first valid image from a FileList (e.g. drag + drop source), as a data URI. This
* method will throw an alert() in case of errors and call back with null.
*
* @param {FileList} fileList The FileList to load.
* @param {Function} callback The callback to fire once image loading is done (or fails).
* @return Returns an object containing 'uri' representing the loaded image. There will also be
* a 'name' field indicating the file name, if one is available.
*/
function loadImageFromFileList(fileList, callback) {
fileList = fileList || [];
var file = null;
for (var i = 0; i < fileList.length; i++) {
if (fileList[i].type.toLowerCase().match(/^image\/(png|jpeg|jpg)/)) {
file = fileList[i];
break;
}
}
if (!file) {
alert(messages[PLEASE_USE_VALID]);
callback(null);
return;
}
var fileReader = new FileReader();
// Closure to capture the file information.
fileReader.onload = function(e) {
callback({
uri: e.target.result,
name: file.name
});
};
fileReader.onerror = function(e) {
switch(e.target.error.code) {
case e.target.error.NOT_FOUND_ERR:
alert('File not found.');
break;
case e.target.error.NOT_READABLE_ERR:
alert('File is not readable.');
break;
case e.target.error.ABORT_ERR:
break; // noop
default:
alert('An error occurred reading this file.');
}
callback(null);
};
fileReader.onabort = function(e) {
alert('File read cancelled.');
callback(null);
};
fileReader.readAsDataURL(file);
}
/**
* Adds a simple version of Canvas.toBlob if toBlob isn't available.
*/
function polyfillCanvasToBlob() {
if (!HTMLCanvasElement.prototype.toBlob && window.Blob) {
HTMLCanvasElement.prototype.toBlob = function(callback, mimeType, quality) {
if (typeof callback != 'function') {
throw new TypeError('Function expected');
}
var dataURL = this.toDataURL(mimeType, quality);
mimeType = dataURL.split(';')[0].split(':')[1];
var bs = window.atob(dataURL.split(',')[1]);
if (dataURL == 'data:,' || !bs.length) {
callback(null);
return;
}
for (var ui8arr = new Uint8Array(bs.length), i = 0; i < bs.length; ++i) {
ui8arr[i] = bs.charCodeAt(i);
}
callback(new Blob([ui8arr.buffer /* req'd for Safari */ || ui8arr], {type: mimeType}));
};
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment