function updatePointerUp( tracker, eventInfo, gPoint, buttonChanged ) {
        var delegate = THIS[ tracker.hash ],
            pointsList = tracker.getActivePointersListByType( gPoint.type ),
            releasePoint,
            releaseTime,
            updateGPoint,
            wasCaptured = false,
            quick;

        if ( typeof eventInfo.originalEvent.buttons !== 'undefined' ) {
            pointsList.buttons = eventInfo.originalEvent.buttons;
        } else {
            if ( buttonChanged === 0 ) {
                // Primary
                pointsList.buttons ^= ~1;
            } else if ( buttonChanged === 1 ) {
                // Aux
                pointsList.buttons ^= ~4;
            } else if ( buttonChanged === 2 ) {
                // Secondary
                pointsList.buttons ^= ~2;
            } else if ( buttonChanged === 3 ) {
                // X1 (Back)
                pointsList.buttons ^= ~8;
            } else if ( buttonChanged === 4 ) {
                // X2 (Forward)
                pointsList.buttons ^= ~16;
            } else if ( buttonChanged === 5 ) {
                // Pen Eraser
                pointsList.buttons ^= ~32;
            }
        }

        eventInfo.shouldCapture = false;

        // Only capture and track primary button, pen, and touch contacts
        if ( buttonChanged !== 0 ) {
            eventInfo.shouldReleaseCapture = false;

            // Aux Release
            if ( tracker.nonPrimaryReleaseHandler &&
                                !eventInfo.preventGesture &&
                                !eventInfo.defaultPrevented ) {
                eventInfo.preventDefault = true;

                tracker.nonPrimaryReleaseHandler(
                    {
                        eventSource:           tracker,
                        pointerType:           gPoint.type,
                        position:              getPointRelativeToAbsolute(gPoint.currentPos, tracker.element),
                        button:                buttonChanged,
                        buttons:               pointsList.buttons,
                        isTouchEvent:          gPoint.type === 'touch',
                        originalEvent:         eventInfo.originalEvent,
                        userData:              tracker.userData
                    }
                );
            }

            return;
        }

        updateGPoint = pointsList.getById( gPoint.id );

        if ( updateGPoint ) {
            pointsList.removeContact();
            //$.console.log('contacts-- ', pointsList.contacts);

            // Update the pointer, stop tracking it if not still in this element
            if ( updateGPoint.captured ) {
                //updateGPoint.captured = false; // Handled by updatePointerCaptured()
                wasCaptured = true;
            }
            updateGPoint.lastPos = updateGPoint.currentPos;
            updateGPoint.lastTime = updateGPoint.currentTime;
            updateGPoint.currentPos = gPoint.currentPos;
            updateGPoint.currentTime = gPoint.currentTime;
            if ( !updateGPoint.insideElement ) {
                stopTrackingPointer( tracker, pointsList, updateGPoint );
            }

            releasePoint = updateGPoint.currentPos;
            releaseTime = updateGPoint.currentTime;
        } else {
            // NOTE: updatePointerUp(): pointerup on untracked gPoint
            // ...we'll start to track pointer again
            gPoint.captured = false; // Handled by updatePointerCaptured()
            gPoint.insideElementPressed = false;
            gPoint.insideElement = true;
            startTrackingPointer( pointsList, gPoint );

            updateGPoint = gPoint;
        }

        if ( !eventInfo.preventGesture && !eventInfo.defaultPrevented ) {
            if ( wasCaptured ) {
                // Pointer was activated in our element but could have been removed in any element since events are captured to our element

                eventInfo.shouldReleaseCapture = true;
                eventInfo.preventDefault = true;

                if ( tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) {
                    $.MouseTracker.gesturePointVelocityTracker.removePoint( tracker, updateGPoint );
                }

                if ( pointsList.contacts === 0 ) {

                    // Release (pressed in our element)
                    if ( tracker.releaseHandler && releasePoint ) {
                        tracker.releaseHandler(
                            {
                                eventSource:           tracker,
                                pointerType:           updateGPoint.type,
                                position:              getPointRelativeToAbsolute( releasePoint, tracker.element ),
                                buttons:               pointsList.buttons,
                                insideElementPressed:  updateGPoint.insideElementPressed,
                                insideElementReleased: updateGPoint.insideElement,
                                isTouchEvent:          updateGPoint.type === 'touch',
                                originalEvent:         eventInfo.originalEvent,
                                userData:              tracker.userData
                            }
                        );
                    }

                    // Drag End
                    if ( tracker.dragEndHandler && delegate.sentDragEvent ) {
                        tracker.dragEndHandler(
                            {
                                eventSource:          tracker,
                                pointerType:          updateGPoint.type,
                                position:             getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ),
                                speed:                updateGPoint.speed,
                                direction:            updateGPoint.direction,
                                shift:                eventInfo.originalEvent.shiftKey,
                                isTouchEvent:         updateGPoint.type === 'touch',
                                originalEvent:        eventInfo.originalEvent,
                                userData:             tracker.userData
                            }
                        );
                    }

                    // We want to clear this flag regardless of whether we fired the dragEndHandler
                    delegate.sentDragEvent = false;

                    // Click / Double-Click
                    if ( ( tracker.clickHandler || tracker.dblClickHandler ) && updateGPoint.insideElement ) {
                        quick = releaseTime - updateGPoint.contactTime <= tracker.clickTimeThreshold &&
                                        updateGPoint.contactPos.distanceTo( releasePoint ) <= tracker.clickDistThreshold;

                        // Click
                        if ( tracker.clickHandler ) {
                            tracker.clickHandler(
                                {
                                    eventSource:          tracker,
                                    pointerType:          updateGPoint.type,
                                    position:             getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ),
                                    quick:                quick,
                                    shift:                eventInfo.originalEvent.shiftKey,
                                    isTouchEvent:         updateGPoint.type === 'touch',
                                    originalEvent:        eventInfo.originalEvent,
                                    originalTarget:       updateGPoint.originalTarget,
                                    userData:             tracker.userData
                                }
                            );
                        }

                        // Double-Click
                        if ( tracker.dblClickHandler && quick ) {
                            pointsList.clicks++;
                            if ( pointsList.clicks === 1 ) {
                                delegate.lastClickPos = releasePoint;
                                /*jshint loopfunc:true*/
                                delegate.dblClickTimeOut = setTimeout( function() {
                                    pointsList.clicks = 0;
                                }, tracker.dblClickTimeThreshold );
                                /*jshint loopfunc:false*/
                            } else if ( pointsList.clicks === 2 ) {
                                clearTimeout( delegate.dblClickTimeOut );
                                pointsList.clicks = 0;
                                if ( delegate.lastClickPos.distanceTo( releasePoint ) <= tracker.dblClickDistThreshold ) {
                                    tracker.dblClickHandler(
                                        {
                                            eventSource:          tracker,
                                            pointerType:          updateGPoint.type,
                                            position:             getPointRelativeToAbsolute( updateGPoint.currentPos, tracker.element ),
                                            shift:                eventInfo.originalEvent.shiftKey,
                                            isTouchEvent:         updateGPoint.type === 'touch',
                                            originalEvent:        eventInfo.originalEvent,
                                            userData:             tracker.userData
                                        }
                                    );
                                }
                                delegate.lastClickPos = null;
                            }
                        }
                    }
                } else if ( pointsList.contacts === 2 ) {
                    if ( tracker.pinchHandler && updateGPoint.type === 'touch' ) {
                        // Reset for pinch
                        delegate.pinchGPoints = pointsList.asArray();
                        delegate.lastPinchDist = delegate.currentPinchDist = delegate.pinchGPoints[ 0 ].currentPos.distanceTo( delegate.pinchGPoints[ 1 ].currentPos );
                        delegate.lastPinchCenter = delegate.currentPinchCenter = getCenterPoint( delegate.pinchGPoints[ 0 ].currentPos, delegate.pinchGPoints[ 1 ].currentPos );
                    }
                }
            } else {
                // Pointer was activated in another element but removed in our element

                eventInfo.shouldReleaseCapture = false;

                // Release (pressed in another element)
                if ( tracker.releaseHandler && releasePoint ) {
                    tracker.releaseHandler(
                        {
                            eventSource:           tracker,
                            pointerType:           updateGPoint.type,
                            position:              getPointRelativeToAbsolute( releasePoint, tracker.element ),
                            buttons:               pointsList.buttons,
                            insideElementPressed:  updateGPoint.insideElementPressed,
                            insideElementReleased: updateGPoint.insideElement,
                            isTouchEvent:          updateGPoint.type === 'touch',
                            originalEvent:         eventInfo.originalEvent,
                            userData:              tracker.userData
                        }
                    );
                    eventInfo.preventDefault = true;
                }
            }
        }
    }
@function
@private
@inner
@param {OpenSeadragon.MouseTracker} tracker
    A reference to the MouseTracker instance.
@param {OpenSeadragon.MouseTracker.EventProcessInfo} eventInfo
    Processing info for originating DOM event.
@param {OpenSeadragon.MouseTracker.GesturePoint} gPoint
     Gesture points associated with the event.
@param {Number} buttonChanged
     The button involved in the event: -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser.
     Note on chorded button presses (a button pressed when another button is already pressed): In the W3C Pointer Events model,
     only one pointerdown/pointerup event combo is fired. Chorded button state changes instead fire pointermove events.