HTML preprocessors can make writing HTML more powerful or convenient. For instance, Markdown is designed to be easier to write and read for text documents and you could write a loop in Pug.
In CodePen, whatever you write in the HTML editor is what goes within the <body>
tags in a basic HTML5 template. So you don't have access to higher-up elements like the <html>
tag. If you want to add classes there that can affect the whole document, this is the place to do it.
In CodePen, whatever you write in the HTML editor is what goes within the <body>
tags in a basic HTML5 template. If you need things in the <head>
of the document, put that code here.
The resource you are linking to is using the 'http' protocol, which may not work when the browser is using https.
CSS preprocessors help make authoring CSS easier. All of them offer things like variables and mixins to provide convenient abstractions.
It's a common practice to apply CSS to a page that styles elements such that they are consistent across all browsers. We offer two of the most popular choices: normalize.css and a reset. Or, choose Neither and nothing will be applied.
To get the best cross-browser support, it is a common practice to apply vendor prefixes to CSS properties and values that require them to work. For instance -webkit-
or -moz-
.
We offer two popular choices: Autoprefixer (which processes your CSS server-side) and -prefix-free (which applies prefixes via a script, client-side).
Any URLs added here will be added as <link>
s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.
You can apply CSS to your Pen from any stylesheet on the web. Just put a URL to it here and we'll apply it, in the order you have them, before the CSS in the Pen itself.
You can also link to another Pen here (use the .css
URL Extension) and we'll pull the CSS from that Pen and include it. If it's using a matching preprocessor, use the appropriate URL Extension and we'll combine the code before preprocessing, so you can use the linked Pen as a true dependency.
JavaScript preprocessors can help make authoring JavaScript easier and more convenient.
Babel includes JSX processing.
Any URL's added here will be added as <script>
s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.
You can apply a script from anywhere on the web to your Pen. Just put a URL to it here and we'll add it, in the order you have them, before the JavaScript in the Pen itself.
If the script you link to has the file extension of a preprocessor, we'll attempt to process it before applying.
You can also link to another Pen here, and we'll pull the JavaScript from that Pen and include it. If it's using a matching preprocessor, we'll combine the code before preprocessing, so you can use the linked Pen as a true dependency.
Search for and use JavaScript packages from npm here. By selecting a package, an import
statement will be added to the top of the JavaScript editor for this package.
Using packages here is powered by esm.sh, which makes packages from npm not only available on a CDN, but prepares them for native JavaScript ESM usage.
All packages are different, so refer to their docs for how they work.
If you're using React / ReactDOM, make sure to turn on Babel for the JSX processing.
If active, Pens will autosave every 30 seconds after being saved once.
If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.
If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.
Visit your global Editor Settings.
<div>
<!-- |======================= Image & Crop =========================== -->
<div class="image-container variable-size">
<!-- ==|===================== Canvases ============================= -->
<div class="l-position-absolute"
onmousedown="event.preventDefault(); event.stopPropagation(); interactionStart(event)"
onmousemove="event.preventDefault(); event.stopPropagation(); interactionMove(event)"
onmouseup="interactionEnd(event)"
onmouseleave="interactionCancel(event)"
ontouchstart="event.preventDefault(); event.stopPropagation(); interactionStart(event)"
ontouchmove="event.preventDefault(); event.stopPropagation(); interactionMove(event)"
ontouchend="interactionEnd(event)"
ontouchcancel="interactionCancel(event)">
<canvas id="canvas-image" class="l-position-absolute canvas-image variable-size"></canvas>
<canvas id="canvas-overlay" class="l-position-absolute canvas-overlay variable-size"></canvas>
</div>
<!-- ==|===================== Drop Zone ============================ -->
<div id="image-drop-zone" class="drop-zone l-position-absolute variable-size"
ondragenter="event.stopPropagation(); event.preventDefault();"
ondragover="event.stopPropagation(); event.preventDefault();"
ondrop="event.preventDefault(); event.stopPropagation(); onDrop(event);">
<p>Drag and drop a JPEG or PNG image file</p>
<p id="px-warning" class="__warning __hide">Image needs to be at least 440px by 440px</p>
</div>
</div>
<!-- |=========================== Buttons ============================ -->
<div class="l-centered">
<span id="file-select"><p>Or select a file... </p>
<input id="file-select-input" type="file" name="file" class="file" onchange="onChange(this.files)">
</input><br>
</span>
<button id="file-clear" class="__hide" type="button" onclick="clearImage()">Clear Image</button><br>
<button id="file-submit" class="__hide" type="submit" onclick="submitRequest()">Submit
</button>
</div>
<!-- |======================= Processed Image ======================== -->
<div id="target" class="l-centered">
</div>
</div>
<script src="./script.js" type="text/javascript"></script>
/* ================================== Base ================================== */
body {
background-color: hsl(200, 20%, 10%);
color: white;
font-family: sans-serif;
}
p {
margin: 2em;
line-height: 2;
}
/* ================================= Module ================================= */
.image-container{
margin: 0 auto 1em;
}
.variable-size{
width: 220px;
height: 220px;
}
.canvas-image {
background-color: hsl(200, 20%, 25%);
}
.canvas-overlay{
background-color: hsl(1, 1%, 1%);
background-color: hsla(1, 1%, 1%, 0.1);
cursor: -webkit-grab;
cursor: -moz-grab;
cursor: grab;
}
.canvas-overlay:active{
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
cursor: grabbing;
}
.drop-zone{
background-color: hsl(200, 20%, 30%);
border: 2px dashed white;
border-radius: 4px;
padding: 0;
color: white;
font-size: 80%;
text-align: center;
overflow: hidden;
}
#file-select p{
margin: auto 0;
font-size: 80%;
display: inline;
}
/* ================================= Layout ================================= */
.l-centered{
width: 320px;
margin: 0 auto;
}
.l-position-absolute{
position: absolute;
}
/* ================================= State ================================== */
.__hide{
display: none;
}
.__warning{
color: hsl(1, 100%, 60%);
}
(function(module){
// ================================ Config ================================ //
/**
* Use RENDERED_IMAGE_WIDTH and RENDERED_IMAGE_HEIGHT to set the target pixel
* dimensions for the file that is rendered and ready for upload to a
* database, etc. These values also define the minimum size, imagePicker will
* respond with an error message if the chosen file has a width or height less
* than the following values.
* In pixels.
*/
const RENDERED_IMAGE_WIDTH = 440;
const RENDERED_IMAGE_HEIGHT = 440;
/**
* Use PREVIEW_IMAGE_WIDTH to set the onscreen pixel dimensions of the
* Imagepicker box to receive the drag and drop action.
* In pixels
*/
const PREVIEW_IMAGE_WIDTH = 220;
/**
* The aspect ratio for preview image follows the aspect ratio of the rendered
* image. PREVIEW_IMAGE_HEIGHT will be equal to RENDERED_IMAGE_HEIGHT
* multplied by the ratio: PREVIEW_IMAGE_WIDTH / RENDERED_IMAGE_WIDTH.
* In pixels.
*/
const PREVIEW_IMAGE_HEIGHT = RENDERED_IMAGE_HEIGHT * PREVIEW_IMAGE_WIDTH / RENDERED_IMAGE_WIDTH;
// =========================== Event Handlers ============================= //
// Drag events
module.onDrop = onDrop;
// Mouse & Touch events
module.interactionStart = interactionStart;
module.interactionMove = interactionMove;
module.interactionEnd = interactionEnd;
module.interactionCancel = interactionCancel;
// Input & Button events
module.clearImage = clearImage;
module.onChange = onChange;
module.submitRequest = submitRequest;
// =========================== Helper Functions =========================== //
const _applyImage = applyImage;
const _setSize = setSize;
const _setSizeAlert = setSizeAlert
const _drawOverlay = drawOverlay;
const _drawCroppedCanvas = drawCroppedCanvas;
const _getImageSize = getImageSize;
// ================================= Init ================================= //
var _canvasWidth = PREVIEW_IMAGE_WIDTH;
var _canvasHeight = PREVIEW_IMAGE_HEIGHT;
var _isEditing = false;
var _overlayOriginX = 0;
var _overlayOriginY = 0;
var _resizeRatio = 1;
var _rawWidth = 0;
var _rawHeight = 0;
var _initialPointerX = 0;
var _initialPointerY = 0;
var _currentX = 0;
var _currentY = 0;
var _previousX = 0;
var _previousY = 0;
var _imageData = null;
var _croppedImageData = null;
var _imageType = '';
// Alter size of HTML elements to config settings
_setSize(PREVIEW_IMAGE_WIDTH, PREVIEW_IMAGE_HEIGHT);
// ========================= Function Definitions ========================= //
/**
* Event handler for receiving drop events. Invokes the onChange event
* associated with the <input> form item for selecting files, passing along
* the dragged & dropped files.
*
* @param {event} evnt
*/
function onDrop(evnt) {
evnt.stopPropagation();
evnt.preventDefault();
if (evnt.dataTransfer.files.length < 1) return;
// TODO: Do something with the file name
// evnt.dataTransfer.files[0].name;
_imageType = evnt.dataTransfer.files[0].type;
onChange(evnt.dataTransfer.files);
};
/**
* Handler for both mouse and touch actions that initiate an interaction:
* onMouseDown and onTouchStart.
*
* @param {event} event
*/
function interactionStart(event){
_isEditing = true;
_initialPointerX = event.pageX;
_initialPointerY = event.pageY;
// Accommodate touch events
if (event.touches && event.touches.length > 0){
_initialPointerX = event.touches.item(0).pageX;
_initialPointerY = event.touches.item(0).pageY;
}
_drawOverlay(event);
}
/**
* Handler for both mouse the touch events that move a pointer across the
* screen: onMouseMove and onTouchMove
*
* @param {event} event
*/
function interactionMove(event){
if (!_isEditing) return;
_drawOverlay(event);
}
/**
* Handler for both mouse and touch events that end an interaction: onMouseUp
* and onTouchEnd.
*/
function interactionEnd(){
if (_isEditing) _drawCroppedCanvas();
_isEditing = false;
_previousX = _currentX;
_previousY = _currentY;
}
/**
* Handler for both mouse and touch events that cancel an interaction:
* onMouseLeave and onTouchCancel
*/
function interactionCancel(){
if (_isEditing) _drawCroppedCanvas();
_isEditing = false;
_previousX = _currentX;
_previousY = _currentY;
}
/**
* Button action to remove the currently selected image and reset to the
* initial state.
*/
function clearImage(){
_imageData = null;
_imageType = '';
_setSizeAlert(false);
document.getElementById('file-submit').className = '__hide';
document.getElementById('file-clear').className = '__hide';
document.getElementById('file-select').className = '';
// Clear the value of the file input HTML element
let filePath = document.getElementById('file-select-input');
filePath.value = '';
_applyImage();
// Clear Proof of Concept div
const targetNode = document.getElementById('target');
const children = targetNode.childNodes;
for (let i = children.length; i > 0; i--){
targetNode.removeChild(targetNode.childNodes.item(i - 1));
}
// Show image drop zone
const dropZone = document.getElementById('image-drop-zone');
dropZone.className = 'drop-zone l-position-absolute';
}
/**
* Handler initiated by change to the file type <input> and for drop actions
* on the image drop zone. Files identified by either action are loaded and
* converted to arrayBuffer objects. On successfully loading the file,
* continues to update UI by calling _applyImage().
*
* @param {Array} tryFiles
*/
function onChange(tryFiles){
_setSizeAlert();
if (!tryFiles || tryFiles.length < 1) return;
let file = tryFiles[0];
document.getElementById('file-select').className = '__hide';
document.getElementById('file-submit').className = '';
document.getElementById('file-clear').className = '';
// TODO: guard against non-image Content-Types
_imageType = file.type;
var fileReader = new FileReader();
fileReader.onloadend = element => {
_imageData = element.target.result;
_applyImage();
};
fileReader.readAsArrayBuffer(file);
};
/**
* Button action to render a final image. As a proof of concept, it draws
* the image as full resolution below the imagePicker div.
*
* @todo Replace this code with work that is relevant to you.
*/
function submitRequest(){
if (!_croppedImageData) return;
// Proof of concept: render image to the screen at full size
const targetNode = document.getElementById('target');
const children = targetNode.childNodes;
for (let i = children.length; i > 0; i--){
targetNode.removeChild(targetNode.childNodes.item(i - 1));
}
// Add full size image
const imageForPoC = new Image(); // RENDERED_IMAGE_WIDTH, RENDERED_IMAGE_HEIGHT
const blobForPoC = new Blob([_croppedImageData], { type: 'image/png' });
imageForPoC.src = window.URL.createObjectURL(blobForPoC);
imageForPoC.onload = function(){
// Draw the image canvas
targetNode.appendChild(imageForPoC);
window.URL.revokeObjectURL(imageForPoC.src);
};
}
/**
* Evaluates the aspect ratio and pixel dimensions of a selected image file.
* Launches methods to draw the image onto the canvas element and draw a crop
* frame in the overlay canvas.
*/
function applyImage() {
_setSize(PREVIEW_IMAGE_WIDTH, PREVIEW_IMAGE_HEIGHT);
_rawWidth = _rawHeight = 0;
if (!_imageData) return;
// __hide image drop zone
const imageDropZone = document.getElementById('image-drop-zone');
imageDropZone.className = '__hide';
const canvas = document.getElementById('canvas-image');
var ctx = canvas.getContext('2d');
const widthAndHeight = _getImageSize(_imageData, _imageType);
if (!widthAndHeight) return console.log('Failed to find image dimensions');
_rawWidth = widthAndHeight.width;
_rawHeight = widthAndHeight.height;
// Guard against images that are too small
if (_rawWidth === 0 && _rawHeight === 0) console.log('failed to read image dimensions');
if (_rawWidth < RENDERED_IMAGE_WIDTH || _rawHeight < RENDERED_IMAGE_HEIGHT){
clearImage();
_setSizeAlert(true);
return;
}
// Identify the resize-ratio and aspect ratio
const initialAspectRatio = PREVIEW_IMAGE_WIDTH / PREVIEW_IMAGE_HEIGHT;
let aspectRatio = 1;
// let landscape = _rawWidth >= _rawHeight;
let landscape = _rawWidth / _rawHeight >= PREVIEW_IMAGE_WIDTH / PREVIEW_IMAGE_HEIGHT;
const big = landscape ? _rawWidth : _rawHeight;
const small = landscape ? _rawHeight : _rawWidth;
aspectRatio = big / small;
_resizeRatio = landscape ? _rawHeight / RENDERED_IMAGE_HEIGHT : _rawWidth / RENDERED_IMAGE_WIDTH;
// TODO: change _canvasWidth to PREVIEW_IMAGE_WIDTH, etc
const finalWidth = landscape ? _canvasWidth * aspectRatio / initialAspectRatio : _canvasWidth;
const finalHeight = landscape ? _canvasHeight : _canvasHeight * aspectRatio * initialAspectRatio;
// Adjust HTML elements to new size
_setSize(finalWidth, finalHeight);
const imageForDraw = new Image(finalWidth, finalHeight);
const blobForDraw = new Blob([_imageData], { type: _imageType });
imageForDraw.src = window.URL.createObjectURL(blobForDraw);
imageForDraw.onload = function(){
// Draw the image canvas
ctx.drawImage(imageForDraw, 0, 0, finalWidth, finalHeight);
};
// Draw an initial overlay
_drawOverlay();
// Create an initial cropped image
_drawCroppedCanvas();
};
/**
* Set size of image canvas and container elements based on raw image size.
*
* @param {number} setWidth
* @param {number} setHeight
*/
function setSize(setWidth, setHeight){
const collectionOfElements = document.getElementsByClassName('variable-size');
for (let n = 0; n < collectionOfElements.length; n++) {
collectionOfElements[n].style.width = `${setWidth}px`;
collectionOfElements[n].style.height = `${setHeight}px`;
}
const canvas = document.getElementById('canvas-image');
_canvasWidth = canvas.width = setWidth;
_canvasHeight = canvas.height = setHeight;
// Remove any lingering crop overlays
const overlayCanvas = document.getElementById('canvas-overlay');
const overlayCtx = overlayCanvas.getContext('2d');
overlayCtx.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height);
}
/**
* Present an error message when the picked image size fails to meet the
* desired minimum size, based on config settings.
*
* @param {bool} showAlert Arg is true to show the image, false to hide it.
*/
function setSizeAlert(showAlert){
let classes = showAlert ? '__warning' : '__warning __hide';
let alertText = document.getElementById('px-warning');
alertText.textContent = `Image needs to be at least ${RENDERED_IMAGE_WIDTH}px by ${RENDERED_IMAGE_HEIGHT}px`;
alertText.className = classes;
}
/**
* Draw a canvas overlay, showing the user a box over the picked image, with
* an aspect ratio based on the desired final rendered image from the config
* settings. Canvas overlay is updated with the movement of the user's mouse
* or touch.
* @param {object} event Mouse or touch events inside the drop zone container.
*/
function drawOverlay(event){
// The crop box size
const cropSize = { width: PREVIEW_IMAGE_WIDTH, height: PREVIEW_IMAGE_HEIGHT };
const cropHalf = { width: cropSize.width / 2, height: cropSize.height / 2 };
const canvas = document.getElementById('canvas-overlay');
canvas.width = _canvasWidth;
canvas.height = _canvasHeight;
// If no event was provided, create an initial overlay, centered
if (!event){
_previousX = _currentX = _canvasWidth / 2;
_previousY = _currentY = _canvasHeight / 2;
}else{
_currentX = _previousX + (event.pageX - _initialPointerX);
_currentY = _previousY + (event.pageY - _initialPointerY);
// Accommodate touch events
if (event.touches && event.touches.length > 0){
_currentX = _previousX + (event.touches.item(0).pageX - _initialPointerX);
_currentY = _previousY + (event.touches.item(0).pageY - _initialPointerY);
}
}
// Keep overlay rect in the frame
if (_currentX < cropHalf.width) _currentX = cropHalf.width;
if (_currentX > _canvasWidth - cropHalf.width) _currentX = _canvasWidth - cropHalf.width;
if (_currentY < cropHalf.height) _currentY = cropHalf.height;
if (_currentY > _canvasHeight - cropHalf.height) _currentY = _canvasHeight - cropHalf.height;
// Draw cropping frame
var ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = 'white';
ctx.strokeRect(_currentX - cropHalf.width, _currentY - cropHalf.height, cropSize.width, cropSize.height);
// Draw semi-transparent overlay outside the crop rect
let landscape = _rawWidth >= _rawHeight;
ctx.fillStyle = 'hsla(1, 1%, 1%, 0.4)';
ctx.fillRect(0, 0, landscape ? _currentX - cropHalf.width : _canvasWidth, landscape ? _canvasHeight : _currentY - cropHalf.height);
ctx.fillRect(landscape ? _currentX + cropHalf.width : 0, landscape ? 0 : _currentY + cropHalf.height, canvas.width, canvas.height);
// Save original at full size resolution
_overlayOriginX = (_currentX - cropHalf.width) * (RENDERED_IMAGE_WIDTH / PREVIEW_IMAGE_WIDTH);
_overlayOriginY = (_currentY - cropHalf.height) * (RENDERED_IMAGE_HEIGHT / PREVIEW_IMAGE_HEIGHT);
}
/**
* Render the final image, with cropping based on the user input from the
* overlay selection. Save final image data to var _croppedImageData.
*/
function drawCroppedCanvas(){
const croppedCanvas = document.createElementNS('http://www.w3.org/1999/xhtml', 'canvas');
croppedCanvas.width = RENDERED_IMAGE_WIDTH;
croppedCanvas.height = RENDERED_IMAGE_HEIGHT;
const imageForDraw = new Image();
const blobForDraw = new Blob([_imageData], { type: _imageType });
imageForDraw.src = window.URL.createObjectURL(blobForDraw);
imageForDraw.onload = () => {
const cropCtx = croppedCanvas.getContext('2d');
cropCtx.drawImage(
imageForDraw,
_overlayOriginX * _resizeRatio,
_overlayOriginY * _resizeRatio,
RENDERED_IMAGE_WIDTH * _resizeRatio,
RENDERED_IMAGE_HEIGHT * _resizeRatio,
0,
0,
RENDERED_IMAGE_WIDTH,
RENDERED_IMAGE_HEIGHT
);
// Convert Canvas -> Blob -> ArrayBuffer
// toBlob() is NOT available in Safari / WebKit. Polyfill courtesy of:
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob
if (!HTMLCanvasElement.prototype.toBlob) {
Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
value: function (callback, type, quality) {
var binStr = atob( this.toDataURL(type, quality).split(',')[1] ),
len = binStr.length,
arr = new Uint8Array(len);
for (var i=0; i<len; i++ ) {
arr[i] = binStr.charCodeAt(i);
}
callback( new Blob( [arr], {type: type || 'image/png'} ) );
}
});
}
croppedCanvas.toBlob( blob => {
var fileReader = new FileReader();
fileReader.onloadend = element => {
_croppedImageData = element.target.result;
// Revoke ObjectURLs after use
window.URL.revokeObjectURL(imageForDraw.src);
};
fileReader.readAsArrayBuffer(blob);
}, 'image/png');
};
}
/**
* Evaluates the pixel size of an image and returns an object. Currently works
* for JPEG and PNG file types.
*
* @param {arrayBuffer} arrayBuffer Raw image data.
* @param {string} imageType File type, expects "image/png" or "image/jpeg"
*
* @return {object} An object with properties for pixel dimensions of the image:
* width: {number}
* height: {number}
* Will return null if the evaluation fails.
*/
function getImageSize(arrayBuffer, imageType){
const MARKER_PREFIX = 255;
const EXIF_APP1_MARKER = 225;
const EXIF_APP1_CONFIRMATION = 1165519206;
const EXIF_SUB_IFD = 34665;
const START_OF_SCAN_MARKER = 218;
const INTEL_BYTE = 73;
const MOTOROLA_BYTE = 77;
const EXIF_IMAGE_WIDTH = 256;
const EXIF_IMAGE_HEIGHT = 257;
// EXIF_ORIENTATION = 274
// EXIF_ORIENTATION = 112
const EXIF_SUB_IFD_WIDTH = 40962;
const EXIF_SUB_IFD_HEIGHT = 40963;
let rawWidth = 0;
let rawHeight = 0;
const dataV = new DataView(arrayBuffer);
function startOfFrame(tag) {
if(
(tag >= 192 && tag <= 195) ||
(tag >= 197 && tag <= 199) ||
(tag >= 201 && tag <= 203) ||
(tag >= 205 && tag <= 207)
) return true;
}
function subIFD(startByte, offsetToFirstIFD, i, isIntelAlign){
let IFDWidth = 0;
let IFDHeight = 0;
let subOffset = dataV.getUint16(startByte + 9 + offsetToFirstIFD + 2 + (i * 12) + (isIntelAlign ? 8 : 10), isIntelAlign);
if (subOffset === 0) return false;
let fullSubOffset = startByte + 9 + subOffset;
let numOfSubs = dataV.getUint16(fullSubOffset, isIntelAlign);
// Bitmask to exit the loop if width and height are found;
let widthFlag = 1, heightFlag = 2, flags = 0;
for (let n = 0; n < numOfSubs; n++){
let entryItem = dataV.getUint16(fullSubOffset + 2 + (n * 12), isIntelAlign);
if (entryItem === EXIF_SUB_IFD_WIDTH){
IFDWidth = dataV.getUint32(fullSubOffset + 2 + (n * 12) + 8, isIntelAlign);
if (flags & heightFlag) return {width: IFDWidth, height: IFDHeight};
flags |= widthFlag;
}
if (entryItem === EXIF_SUB_IFD_HEIGHT){
IFDHeight = dataV.getUint32(fullSubOffset + 2 + (n * 12) + 8, isIntelAlign);
if (flags & widthFlag) return {width: IFDWidth, height: IFDHeight};
flags |= heightFlag;
}
}
}
if (imageType === 'image/png'){
rawWidth = dataV.getUint32(16);
rawHeight = dataV.getUint32(20);
return { width: rawWidth, height: rawHeight };
}else if (imageType === 'image/jpeg'){
// While(! start of scan marker){... could be used in place of a for loop,
// but the for loop will guard against a corrupt file that lacks a start
// of scan marker.
for(let x = 0; x < 40000; x++){
if (dataV.getUint8(x) === MARKER_PREFIX){
let byte = x + 1;
// APP1 Marker (EXIF)
if (dataV.getUint8(byte) === EXIF_APP1_MARKER){
// Check if EXIF header marker or APP1 Marker was false positive
if (dataV.getUint32(byte + 3) !== EXIF_APP1_CONFIRMATION) continue;
// 73 73: Intel byte align uses little endian
// 77 77: Motorola byte align uses big endian
let isIntelAlign = false;
if (dataV.getUint8(byte + 9) === INTEL_BYTE && dataV.getUint8(byte + 10) === INTEL_BYTE) isIntelAlign = true;
// Offset to the first IFD
const offsetToFirstIFD = dataV.getUint32(byte + 13, isIntelAlign)
const entryCount = dataV.getUint16(byte + 9 + offsetToFirstIFD, isIntelAlign);
// Each entry is 12 bytes:
// 2 bytes for tag number
// 2 bytes for data type
// 4 bytes for number of components
// 4 bytes for data (or offset to data)
for (let i = 0; i < entryCount; i++){
let tagNum = dataV.getUint16(byte + 9 + offsetToFirstIFD + 2 + (i * 12), isIntelAlign);
// Found Image Width (however for some files this is thumbnail width)
if (tagNum === EXIF_IMAGE_WIDTH){
let actualWidth = dataV.getUint16(byte + 9 + offsetToFirstIFD + 2 + (i * 12) + 8, isIntelAlign);
if (!rawWidth) rawWidth = actualWidth;
}
// Found Image Height (however for some files this is thumbnail height)
if (tagNum === EXIF_IMAGE_HEIGHT){
let actualHeight = dataV.getUint16(byte + 9 + offsetToFirstIFD + 2 + (i * 12) + 8, isIntelAlign);
if (!rawHeight) rawHeight = actualHeight;
}
// EXIF SubIFD marker
// EXIF SubIFD is authoritative. If height and width properties
// exist here, they take precedence and can be trusted to be
// definitive.
if (tagNum === EXIF_SUB_IFD){
const IFDResult = subIFD(byte, offsetToFirstIFD, i, isIntelAlign);
if (!IFDResult) continue;
if (!IFDResult.width || !IFDResult.height) return null;
return IFDResult;
}
}
}
// Start-Of-Frame marker
if (startOfFrame(dataV.getUint8(byte))){
if (!rawHeight) rawHeight = dataV.getUint16(byte + 4);
if (!rawWidth) rawWidth = dataV.getUint16(byte + 6);
}
// Start-Of-Scan marker, stop looking for markers
if (dataV.getUint8(byte) === START_OF_SCAN_MARKER) {
break;
}
}
}
// Return dimensions if valid
if (rawWidth && rawHeight) return { width: rawWidth, height: rawHeight };
// Failed to find dimensions
return null;
}else{
// Unrecognized image type
return null;
}
}
}(window))
Also see: Tab Triggers