Last active
July 28, 2021 18:00
-
-
Save johnjohndoe/0cfee6811b0a9de90c6e56b65fe60cb4 to your computer and use it in GitHub Desktop.
Google Android device art generator. From https://developer.android.com/distribute/marketing-tools/device-art-resources/device-art-generator.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> '; | |
} | |
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