/*
* imgAreaSelect jQuery plugin
* version 0.9.10-wp-6.2
*
* Copyright (c) 2008-2013 Michal Wojciechowski (odyniec.net)
*
* Dual licensed under the MIT (MIT-LICENSE.txt)
* and GPL (GPL-LICENSE.txt) licenses.
*
* https://github.com/odyniec/imgareaselect
*
*/
(function($) {
/*
* Math functions will be used extensively, so it's convenient to make a few
* shortcuts
*/
var abs = Math.abs,
max = Math.max,
min = Math.min,
floor = Math.floor;
/**
* Create a new HTML div element
*
* @return A jQuery object representing the new element
*/
function div() {
return $('<div/>');
}
/**
* imgAreaSelect initialization
*
* @param img
* A HTML image element to attach the plugin to
* @param options
* An options object
*/
$.imgAreaSelect = function (img, options) {
var
/* jQuery object representing the image */
$img = $(img),
/* Has the image finished loading? */
imgLoaded,
/* Plugin elements */
/* Container box */
$box = div(),
/* Selection area */
$area = div(),
/* Border (four divs) */
$border = div().add(div()).add(div()).add(div()),
/* Outer area (four divs) */
$outer = div().add(div()).add(div()).add(div()),
/* Handles (empty by default, initialized in setOptions()) */
$handles = $([]),
/*
* Additional element to work around a cursor problem in Opera
* (explained later)
*/
$areaOpera,
/* Image position (relative to viewport) */
left, top,
/* Image offset (as returned by .offset()) */
imgOfs = { left: 0, top: 0 },
/* Image dimensions (as returned by .width() and .height()) */
imgWidth, imgHeight,
/*
* jQuery object representing the parent element that the plugin
* elements are appended to
*/
$parent,
/* Parent element offset (as returned by .offset()) */
parOfs = { left: 0, top: 0 },
/* Base z-index for plugin elements */
zIndex = 0,
/* Plugin elements position */
position = 'absolute',
/* X/Y coordinates of the starting point for move/resize operations */
startX, startY,
/* Horizontal and vertical scaling factors */
scaleX, scaleY,
/* Current resize mode ("nw", "se", etc.) */
resize,
/* Selection area constraints */
minWidth, minHeight, maxWidth, maxHeight,
/* Aspect ratio to maintain (floating point number) */
aspectRatio,
/* Are the plugin elements currently displayed? */
shown,
/* Current selection (relative to parent element) */
x1, y1, x2, y2,
/* Current selection (relative to scaled image) */
selection = { x1: 0, y1: 0, x2: 0, y2: 0, width: 0, height: 0 },
/* Document element */
docElem = document.documentElement,
/* User agent */
ua = navigator.userAgent,
/* Various helper variables used throughout the code */
$p, d, i, o, w, h, adjusted;
/*
* Translate selection coordinates (relative to scaled image) to viewport
* coordinates (relative to parent element)
*/
/**
* Translate selection X to viewport X
*
* @param x
* Selection X
* @return Viewport X
*/
function viewX(x) {
return x + imgOfs.left - parOfs.left;
}
/**
* Translate selection Y to viewport Y
*
* @param y
* Selection Y
* @return Viewport Y
*/
function viewY(y) {
return y + imgOfs.top - parOfs.top;
}
/*
* Translate viewport coordinates to selection coordinates
*/
/**
* Translate viewport X to selection X
*
* @param x
* Viewport X
* @return Selection X
*/
function selX(x) {
return x - imgOfs.left + parOfs.left;
}
/**
* Translate viewport Y to selection Y
*
* @param y
* Viewport Y
* @return Selection Y
*/
function selY(y) {
return y - imgOfs.top + parOfs.top;
}
/*
* Translate event coordinates (relative to document) to viewport
* coordinates
*/
/**
* Get event X and translate it to viewport X
*
* @param event
* The event object
* @return Viewport X
*/
function evX(event) {
return max(event.pageX || 0, touchCoords(event).x) - parOfs.left;
}
/**
* Get event Y and translate it to viewport Y
*
* @param event
* The event object
* @return Viewport Y
*/
function evY(event) {
return max(event.pageY || 0, touchCoords(event).y) - parOfs.top;
}
/**
* Get X and Y coordinates of a touch event
*
* @param event
* The event object
* @return Coordinates object
*/
function touchCoords(event) {
var oev = event.originalEvent || {};
if (oev.touches && oev.touches.length)
return { x: oev.touches[0].pageX, y: oev.touches[0].pageY };
else
return { x: 0, y: 0 };
}
/**
* Get the current selection
*
* @param noScale
* If set to <code>true</code>, scaling is not applied to the
* returned selection
* @return Selection object
*/
function getSelection(noScale) {
var sx = noScale || scaleX, sy = noScale || scaleY;
return { x1: floor(selection.x1 * sx),
y1: floor(selection.y1 * sy),
x2: floor(selection.x2 * sx),
y2: floor(selection.y2 * sy),
width: floor(selection.x2 * sx) - floor(selection.x1 * sx),
height: floor(selection.y2 * sy) - floor(selection.y1 * sy) };
}
/**
* Set the current selection
*
* @param x1
* X coordinate of the upper left corner of the selection area
* @param y1
* Y coordinate of the upper left corner of the selection area
* @param x2
* X coordinate of the lower right corner of the selection area
* @param y2
* Y coordinate of the lower right corner of the selection area
* @param noScale
* If set to <code>true</code>, scaling is not applied to the
* new selection
*/
function setSelection(x1, y1, x2, y2, noScale) {
var sx = noScale || scaleX, sy = noScale || scaleY;
selection = {
x1: floor(x1 / sx || 0),
y1: floor(y1 / sy || 0),
x2: floor(x2 / sx || 0),
y2: floor(y2 / sy || 0)
};
selection.width = selection.x2 - selection.x1;
selection.height = selection.y2 - selection.y1;
}
/**
* Recalculate image and parent offsets
*/
function adjust() {
/*
* Do not adjust if image has not yet loaded or if width is not a
* positive number. The latter might happen when imgAreaSelect is put
* on a parent element which is then hidden.
*/
if (!imgLoaded || !$img.width())
return;
/*
* Get image offset. The .offset() method returns float values, so they
* need to be rounded.
*/
imgOfs = { left: floor($img.offset().left), top: floor($img.offset().top) };
/* Get image dimensions */
imgWidth = $img.innerWidth();
imgHeight = $img.innerHeight();
imgOfs.top += ($img.outerHeight() - imgHeight) >> 1;
imgOfs.left += ($img.outerWidth() - imgWidth) >> 1;
/* Set minimum and maximum selection area dimensions */
minWidth = floor(options.minWidth / scaleX) || 0;
minHeight = floor(options.minHeight / scaleY) || 0;
maxWidth = floor(min(options.maxWidth / scaleX || 1<<24, imgWidth));
maxHeight = floor(min(options.maxHeight / scaleY || 1<<24, imgHeight));
/*
* Workaround for jQuery 1.3.2 incorrect offset calculation, originally
* observed in Safari 3. Firefox 2 is also affected.
*/
if ($().jquery == '1.3.2' && position == 'fixed' &&
!docElem['getBoundingClientRect'])
{
imgOfs.top += max(document.body.scrollTop, docElem.scrollTop);
imgOfs.left += max(document.body.scrollLeft, docElem.scrollLeft);
}
/* Determine parent element offset */
parOfs = /absolute|relative/.test($parent.css('position')) ?
{ left: floor($parent.offset().left) - $parent.scrollLeft(),
top: floor($parent.offset().top) - $parent.scrollTop() } :
position == 'fixed' ?
{ left: $(document).scrollLeft(), top: $(document).scrollTop() } :
{ left: 0, top: 0 };
left = viewX(0);
top = viewY(0);
/*
* Check if selection area is within image boundaries, adjust if
* necessary
*/
if (selection.x2 > imgWidth || selection.y2 > imgHeight)
doResize();
}
/**
* Update plugin elements
*
* @param resetKeyPress
* If set to <code>false</code>, this instance's keypress
* event handler is not activated
*/
function update(resetKeyPress) {
/* If plugin elements are hidden, do nothing */
if (!shown) return;
/*
* Set the position and size of the container box and the selection area
* inside it
*/
$box.css({ left: viewX(selection.x1), top: viewY(selection.y1) })
.add($area).width(w = selection.width).height(h = selection.height);
/*
* Reset the position of selection area, borders, and handles (IE6/IE7
* position them incorrectly if we don't do this)
*/
$area.add($border).add($handles).css({ left: 0, top: 0 });
/* Set border dimensions */
$border
.width(max(w - $border.outerWidth() + $border.innerWidth(), 0))
.height(max(h - $border.outerHeight() + $border.innerHeight(), 0));
/* Arrange the outer area elements */
$($outer[0]).css({ left: left, top: top,
width: selection.x1, height: imgHeight });
$($outer[1]).css({ left: left + selection.x1, top: top,
width: w, height: selection.y1 });
$($outer[2]).css({ left: left + selection.x2, top: top,
width: imgWidth - selection.x2, height: imgHeight });
$($outer[3]).css({ left: left + selection.x1, top: top + selection.y2,
width: w, height: imgHeight - selection.y2 });
w -= $handles.outerWidth();
h -= $handles.outerHeight();
/* Arrange handles */
switch ($handles.length) {
case 8:
$($handles[4]).css({ left: w >> 1 });
$($handles[5]).css({ left: w, top: h >> 1 });
$($handles[6]).css({ left: w >> 1, top: h });
$($handles[7]).css({ top: h >> 1 });
case 4:
$handles.slice(1,3).css({ left: w });
$handles.slice(2,4).css({ top: h });
}
if (resetKeyPress !== false) {
/*
* Need to reset the document keypress event handler -- unbind the
* current handler
*/
if ($.imgAreaSelect.onKeyPress != docKeyPress)
$(document).off($.imgAreaSelect.keyPress,
$.imgAreaSelect.onKeyPress);
if (options.keys)
/*
* Set the document keypress event handler to this instance's
* docKeyPress() function
*/
$(document).on( $.imgAreaSelect.keyPress, function() {
$.imgAreaSelect.onKeyPress = docKeyPress;
});
}
/*
* Internet Explorer displays 1px-wide dashed borders incorrectly by
* filling the spaces between dashes with white. Toggling the margin
* property between 0 and "auto" fixes this in IE6 and IE7 (IE8 is still
* broken). This workaround is not perfect, as it requires setTimeout()
* and thus causes the border to flicker a bit, but I haven't found a
* better solution.
*
* Note: This only happens with CSS borders, set with the borderWidth,
* borderOpacity, borderColor1, and borderColor2 options (which are now
* deprecated). Borders created with GIF background images are fine.
*/
if (msie && $border.outerWidth() - $border.innerWidth() == 2) {
$border.css('margin', 0);
setTimeout(function () { $border.css('margin', 'auto'); }, 0);
}
}
/**
* Do the complete update sequence: recalculate offsets, update the
* elements, and set the correct values of x1, y1, x2, and y2.
*
* @param resetKeyPress
* If set to <code>false</code>, this instance's keypress
* event handler is not activated
*/
function doUpdate(resetKeyPress) {
adjust();
update(resetKeyPress);
updateSelectionRelativeToParentElement();
}
/**
* Set the correct values of x1, y1, x2, and y2.
*/
function updateSelectionRelativeToParentElement() {
x1 = viewX(selection.x1); y1 = viewY(selection.y1);
x2 = viewX(selection.x2); y2 = viewY(selection.y2);
}
/**
* Hide or fade out an element (or multiple elements)
*
* @param $elem
* A jQuery object containing the element(s) to hide/fade out
* @param fn
* Callback function to be called when fadeOut() completes
*/
function hide($elem, fn) {
options.fadeSpeed ? $elem.fadeOut(options.fadeSpeed, fn) : $elem.hide();
}
/**
* Selection area mousemove event handler
*
* @param event
* The event object
*/
function areaMouseMove(event) {
var x = selX(evX(event)) - selection.x1,
y = selY(evY(event)) - selection.y1;
if (!adjusted) {
adjust();
adjusted = true;
$box.one('mouseout', function () { adjusted = false; });
}
/* Clear the resize mode */
resize = '';
if (options.resizable) {
/*
* Check if the mouse pointer is over the resize margin area and set
* the resize mode accordingly
*/
if (y <= options.resizeMargin)
resize = 'n';
else if (y >= selection.height - options.resizeMargin)
resize = 's';
if (x <= options.resizeMargin)
resize += 'w';
else if (x >= selection.width - options.resizeMargin)
resize += 'e';
}
$box.css('cursor', resize ? resize + '-resize' :
options.movable ? 'move' : '');
if ($areaOpera)
$areaOpera.toggle();
}
/**
* Document mouseup event handler
*
* @param event
* The event object
*/
function docMouseUp(event) {
/* Set back the default cursor */
$('body').css('cursor', '');
/*
* If autoHide is enabled, or if the selection has zero width/height,
* hide the selection and the outer area
*/
if (options.autoHide || selection.width * selection.height == 0)
hide($box.add($outer), function () { $(this).hide(); });
$(document).off('mousemove touchmove', selectingMouseMove);
$box.on('mousemove touchmove', areaMouseMove);
options.onSelectEnd(img, getSelection());
}
/**
* Selection area mousedown event handler
*
* @param event
* The event object
* @return false
*/
function areaMouseDown(event) {
if (event.type == 'mousedown' && event.which != 1) return false;
/*
* With mobile browsers, there is no "moving the pointer over" action,
* so we need to simulate one mousemove event happening prior to
* mousedown/touchstart.
*/
areaMouseMove(event);
adjust();
if (resize) {
/* Resize mode is in effect */
$('body').css('cursor', resize + '-resize');
x1 = viewX(selection[/w/.test(resize) ? 'x2' : 'x1']);
y1 = viewY(selection[/n/.test(resize) ? 'y2' : 'y1']);
$(document).on('mousemove touchmove', selectingMouseMove)
.one('mouseup touchend', docMouseUp);
$box.off('mousemove touchmove', areaMouseMove);
}
else if (options.movable) {
startX = left + selection.x1 - evX(event);
startY = top + selection.y1 - evY(event);
$box.off('mousemove touchmove', areaMouseMove);
$(document).on('mousemove touchmove', movingMouseMove)
.one('mouseup touchend', function () {
options.onSelectEnd(img, getSelection());
$(document).off('mousemove touchmove', movingMouseMove);
$box.on('mousemove touchmove', areaMouseMove);
});
}
else
$img.mousedown(event);
return false;
}
/**
* Adjust the x2/y2 coordinates to maintain aspect ratio (if defined)
*
* @param xFirst
* If set to <code>true</code>, calculate x2 first. Otherwise,
* calculate y2 first.
*/
function fixAspectRatio(xFirst) {
if (aspectRatio)
if (xFirst) {
x2 = max(left, min(left + imgWidth,
x1 + abs(y2 - y1) * aspectRatio * (x2 > x1 || -1)));
y2 = floor(max(top, min(top + imgHeight,
y1 + abs(x2 - x1) / aspectRatio * (y2 > y1 || -1))));
x2 = floor(x2);
}
else {
y2 = max(top, min(top + imgHeight,
y1 + abs(x2 - x1) / aspectRatio * (y2 > y1 || -1)));
x2 = floor(max(left, min(left + imgWidth,
x1 + abs(y2 - y1) * aspectRatio * (x2 > x1 || -1))));
y2 = floor(y2);
}
}
/**
* Resize the selection area respecting the minimum/maximum dimensions and
* aspect ratio
*/
function doResize() {
/*
* Make sure x1, x2, y1, y2 are initialized to avoid the following calculation
* getting incorrect results.
*/
if ( x1 == null || x2 == null || y1 == null || y2 == null ) {
updateSelectionRelativeToParentElement();
}
/*
* Make sure the top left corner of the selection area stays within
* image boundaries (it might not if the image source was dynamically
* changed).
*/
x1 = min(x1, left + imgWidth);
y1 = min(y1, top + imgHeight);
if (abs(x2 - x1) < minWidth) {
/* Selection width is smaller than minWidth */
x2 = x1 - minWidth * (x2 < x1 || -1);
if (x2 < left)
x1 = left + minWidth;
else if (x2 > left + imgWidth)
x1 = left + imgWidth - minWidth;
}
if (abs(y2 - y1) < minHeight) {
/* Selection height is smaller than minHeight */
y2 = y1 - minHeight * (y2 < y1 || -1);
if (y2 < top)
y1 = top + minHeight;
else if (y2 > top + imgHeight)
y1 = top + imgHeight - minHeight;
}
x2 = max(left, min(x2, left + imgWidth));
y2 = max(top, min(y2, top + imgHeight));
fixAspectRatio(abs(x2 - x1) < abs(y2 - y1) * aspectRatio);
if (abs(x2 - x1) > maxWidth) {
/* Selection width is greater than maxWidth */
x2 = x1 - maxWidth * (x2 < x1 || -1);
fixAspectRatio();
}
if (abs(y2 - y1) > maxHeight) {
/* Selection height is greater than maxHeight */
y2 = y1 - maxHeight * (y2 < y1 || -1);
fixAspectRatio(true);
}
selection = { x1: selX(min(x1, x2)), x2: selX(max(x1, x2)),
y1: selY(min(y1, y2)), y2: selY(max(y1, y2)),
width: abs(x2 - x1), height: abs(y2 - y1) };
update();
options.onSelectChange(img, getSelection());
}
/**
* Mousemove event handler triggered when the user is selecting an area
*
* @param event
* The event object
* @return false
*/
function selectingMouseMove(event) {
x2 = /w|e|^$/.test(resize) || aspectRatio ? evX(event) : viewX(selection.x2);
y2 = /n|s|^$/.test(resize) || aspectRatio ? evY(event) : viewY(selection.y2);
doResize();
return false;
}
/**
* Move the selection area
*
* @param newX1
* New viewport X1
* @param newY1
* New viewport Y1
*/
function doMove(newX1, newY1) {
x2 = (x1 = newX1) + selection.width;
y2 = (y1 = newY1) + selection.height;
$.extend(selection, { x1: selX(x1), y1: selY(y1), x2: selX(x2),
y2: selY(y2) });
update();
options.onSelectChange(img, getSelection());
}
/**
* Mousemove event handler triggered when the selection area is being moved
*
* @param event
* The event object
* @return false
*/
function movingMouseMove(event) {
x1 = max(left, min(startX + evX(event), left + imgWidth - selection.width));
y1 = max(top, min(startY + evY(event), top + imgHeight - selection.height));
doMove(x1, y1);
event.preventDefault();
return false;
}
/**
* Start selection
*/
function startSelection() {
$(document).off('mousemove touchmove', startSelection);
adjust();
x2 = x1;
y2 = y1;
doResize();
resize = '';
if (!$outer.is(':visible'))
/* Show the plugin elements */
$box.add($outer).hide().fadeIn(options.fadeSpeed||0);
shown = true;
$(document).off('mouseup touchend', cancelSelection)
.on('mousemove touchmove', selectingMouseMove)
.one('mouseup touchend', docMouseUp);
$box.off('mousemove touchmove', areaMouseMove);
options.onSelectStart(img, getSelection());
}
/**
* Cancel selection
*/
function cancelSelection() {
$(document).off('mousemove touchmove', startSelection)
.off('mouseup touchend', cancelSelection);
hide($box.add($outer));
setSelection(selX(x1), selY(y1), selX(x1), selY(y1));
/* If this is an API call, callback functions should not be triggered */
if (!(this instanceof $.imgAreaSelect)) {
options.onSelectChange(img, getSelection());
options.onSelectEnd(img, getSelection());
}
}
/**
* Image mousedown event handler
*
* @param event
* The event object
* @return false
*/
function imgMouseDown(event) {
/* Ignore the event if animation is in progress */
if (event.which > 1 || $outer.is(':animated')) return false;
adjust();
startX = x1 = evX(event);
startY = y1 = evY(event);
/* Selection will start when the mouse is moved */
$(document).on({ 'mousemove touchmove': startSelection,
'mouseup touchend': cancelSelection });
return false;
}
/**
* Window resize event handler
*/
function windowResize() {
doUpdate(false);
}
/**
* Image load event handler. This is the final part of the initialization
* process.
*/
function imgLoad() {
imgLoaded = true;
/* Set options */
setOptions(options = $.extend({
classPrefix: 'imgareaselect',
movable: true,
parent: 'body',
resizable: true,
resizeMargin: 10,
onInit: function () {},
onSelectStart: function () {},
onSelectChange: function () {},
onSelectEnd: function () {}
}, options));
$box.add($outer).css({ visibility: '' });
if (options.show) {
shown = true;
adjust();
update();
$box.add($outer).hide().fadeIn(options.fadeSpeed||0);
}
/*
* Call the onInit callback. The setTimeout() call is used to ensure
* that the plugin has been fully initialized and the object instance is
* available (so that it can be obtained in the callback).
*/
setTimeout(function () { options.onInit(img, getSelection()); }, 0);
}
/**
* Document keypress event handler
*
* @param event
* The event object
* @return false
*/
var docKeyPress = function(event) {
var k = options.keys, d, t, key = event.keyCode;
d = !isNaN(k.alt) && (event.altKey || event.originalEvent.altKey) ? k.alt :
!isNaN(k.ctrl) && event.ctrlKey ? k.ctrl :
!isNaN(k.shift) && event.shiftKey ? k.shift :
!isNaN(k.arrows) ? k.arrows : 10;
if (k.arrows == 'resize' || (k.shift == 'resize' && event.shiftKey) ||
(k.ctrl == 'resize' && event.ctrlKey) ||
(k.alt == 'resize' && (event.altKey || event.originalEvent.altKey)))
{
/* Resize selection */
switch (key) {
case 37:
/* Left */
d = -d;
case 39:
/* Right */
t = max(x1, x2);
x1 = min(x1, x2);
x2 = max(t + d, x1);
fixAspectRatio();
break;
case 38:
/* Up */
d = -d;
case 40:
/* Down */
t = max(y1, y2);
y1 = min(y1, y2);
y2 = max(t + d, y1);
fixAspectRatio(true);
break;
default:
return;
}
doResize();
}
else {
/* Move selection */
x1 = min(x1, x2);
y1 = min(y1, y2);
switch (key) {
case 37:
/* Left */
doMove(max(x1 - d, left), y1);
break;
case 38:
/* Up */
doMove(x1, max(y1 - d, top));
break;
case 39:
/* Right */
doMove(x1 + min(d, imgWidth - selX(x2)), y1);
break;
case 40:
/* Down */
doMove(x1, y1 + min(d, imgHeight - selY(y2)));
break;
default:
return;
}
}
return false;
};
/**
* Apply style options to plugin element (or multiple elements)
*
* @param $elem
* A jQuery object representing the element(s) to style
* @param props
* An object that maps option names to corresponding CSS
* properties
*/
function styleOptions($elem, props) {
for (var option in props)
if (options[option] !== undefined)
$elem.css(props[option], options[option]);
}
/**
* Set plugin options
*
* @param newOptions
* The new options object
*/
function setOptions(newOptions) {
if (newOptions.parent)
($parent = $(newOptions.parent)).append($box.add($outer));
/* Merge the new options with the existing ones */
$.extend(options, newOptions);
adjust();
if (newOptions.handles != null) {
/* Recreate selection area handles */
$handles.remove();
$handles = $([]);
i = newOptions.handles ? newOptions.handles == 'corners' ? 4 : 8 : 0;
while (i--)
$handles = $handles.add(div());
/* Add a class to handles and set the CSS properties */
$handles.addClass(options.classPrefix + '-handle').css({
position: 'absolute',
/*
* The font-size property needs to be set to zero, otherwise
* Internet Explorer makes the handles too large
*/
fontSize: '0',
zIndex: zIndex + 1 || 1
});
/*
* If handle width/height has not been set with CSS rules, set the
* default 5px
*/
if (!parseInt($handles.css('width')) >= 0)
$handles.width(10).height(10);
/*
* If the borderWidth option is in use, add a solid border to
* handles
*/
if (o = options.borderWidth)
$handles.css({ borderWidth: o, borderStyle: 'solid' });
/* Apply other style options */
styleOptions($handles, { borderColor1: 'border-color',
borderColor2: 'background-color',
borderOpacity: 'opacity' });
}
/* Calculate scale factors */
scaleX = options.imageWidth / imgWidth || 1;
scaleY = options.imageHeight / imgHeight || 1;
/* Set selection */
if (newOptions.x1 != null) {
setSelection(newOptions.x1, newOptions.y1, newOptions.x2,
newOptions.y2);
newOptions.show = !newOptions.hide;
}
if (newOptions.keys)
/* Enable keyboard support */
options.keys = $.extend({ shift: 1, ctrl: 'resize' },
newOptions.keys);
/* Add classes to plugin elements */
$outer.addClass(options.classPrefix + '-outer');
$area.addClass(options.classPrefix + '-selection');
for (i = 0; i++ < 4;)
$($border[i-1]).addClass(options.classPrefix + '-border' + i);
/* Apply style options */
styleOptions($area, { selectionColor: 'background-color',
selectionOpacity: 'opacity' });
styleOptions($border, { borderOpacity: 'opacity',
borderWidth: 'border-width' });
styleOptions($outer, { outerColor: 'background-color',
outerOpacity: 'opacity' });
if (o = options.borderColor1)
$($border[0]).css({ borderStyle: 'solid', borderColor: o });
if (o = options.borderColor2)
$($border[1]).css({ borderStyle: 'dashed', borderColor: o });
/* Append all the selection area elements to the container box */
$box.append($area.add($border).add($areaOpera)).append($handles);
if (msie) {
if (o = ($outer.css('filter')||'').match(/opacity=(\d+)/))
$outer.css('opacity', o[1]/100);
if (o = ($border.css('filter')||'').match(/opacity=(\d+)/))
$border.css('opacity', o[1]/100);
}
if (newOptions.hide)
hide($box.add($outer));
else if (newOptions.show && imgLoaded) {
shown = true;
$box.add($outer).fadeIn(options.fadeSpeed||0);
doUpdate();
}
/* Calculate the aspect ratio factor */
aspectRatio = (d = (options.aspectRatio || '').split(/:/))[0] / d[1];
$img.add($outer).off('mousedown', imgMouseDown);
if (options.disable || options.enable === false) {
/* Disable the plugin */
$box.off({ 'mousemove touchmove': areaMouseMove,
'mousedown touchstart': areaMouseDown });
$(window).off('resize', windowResize);
}
else {
if (options.enable || options.disable === false) {
/* Enable the plugin */
if (options.resizable || options.movable)
$box.on({ 'mousemove touchmove': areaMouseMove,
'mousedown touchstart': areaMouseDown });
$(window).on( 'resize', windowResize);
}
if (!options.persistent)
$img.add($outer).on('mousedown touchstart', imgMouseDown);
}
options.enable = options.disable = undefined;
}
/**
* Remove plugin completely
*/
this.remove = function () {
/*
* Call setOptions with { disable: true } to unbind the event handlers
*/
setOptions({ disable: true });
$box.add($outer).remove();
};
/*
* Public API
*/
/**
* Get current options
*
* @return An object containing the set of options currently in use
*/
this.getOptions = function () { return options; };
/**
* Set plugin options
*
* @param newOptions
* The new options object
*/
this.setOptions = setOptions;
/**
* Get the current selection
*
* @param noScale
* If set to <code>true</code>, scaling is not applied to the
* returned selection
* @return Selection object
*/
this.getSelection = getSelection;
/**
* Set the current selection
*
* @param x1
* X coordinate of the upper left corner of the selection area
* @param y1
* Y coordinate of the upper left corner of the selection area
* @param x2
* X coordinate of the lower right corner of the selection area
* @param y2
* Y coordinate of the lower right corner of the selection area
* @param noScale
* If set to <code>true</code>, scaling is not applied to the
* new selection
*/
this.setSelection = setSelection;
/**
* Cancel selection
*/
this.cancelSelection = cancelSelection;
/**
* Update plugin elements
*
* @param resetKeyPress
* If set to <code>false</code>, this instance's keypress
* event handler is not activated
*/
this.update = doUpdate;
/* Do the dreaded browser detection */
var msie = (/msie ([\w.]+)/i.exec(ua)||[])[1],
opera = /opera/i.test(ua),
safari = /webkit/i.test(ua) && !/chrome/i.test(ua);
/*
* Traverse the image's parent elements (up to <body>) and find the
* highest z-index
*/
$p = $img;
while ($p.length) {
zIndex = max(zIndex,
!isNaN($p.css('z-index')) ? $p.css('z-index') : zIndex);
/* Also check if any of the ancestor elements has fixed position */
if ($p.css('position') == 'fixed')
position = 'fixed';
$p = $p.parent(':not(body)');
}
/*
* If z-index is given as an option, it overrides the one found by the
* above loop
*/
zIndex = options.zIndex || zIndex;
if (msie)
$img.attr('unselectable', 'on');
/*
* In MSIE and WebKit, we need to use the keydown event instead of keypress
*/
$.imgAreaSelect.keyPress = msie || safari ? 'keydown' : 'keypress';
/*
* There is a bug affecting the CSS cursor property in Opera (observed in
* versions up to 10.00) that prevents the cursor from being updated unless
* the mouse leaves and enters the element again. To trigger the mouseover
* event, we're adding an additional div to $box and we're going to toggle
* it when mouse moves inside the selection area.
*/
if (opera)
$areaOpera = div().css({ width: '100%', height: '100%',
position: 'absolute', zIndex: zIndex + 2 || 2 });
/*
* We initially set visibility to "hidden" as a workaround for a weird
* behaviour observed in Google Chrome 1.0.154.53 (on Windows XP). Normally
* we would just set display to "none", but, for some reason, if we do so
* then Chrome refuses to later display the element with .show() or
* .fadeIn().
*/
$box.add($outer).css({ visibility: 'hidden', position: position,
overflow: 'hidden', zIndex: zIndex || '0' });
$box.css({ zIndex: zIndex + 2 || 2 });
$area.add($border).css({ position: 'absolute', fontSize: '0' });
/*
* If the image has been fully loaded, or if it is not really an image (eg.
* a div), call imgLoad() immediately; otherwise, bind it to be called once
* on image load event.
*/
img.complete || img.readyState == 'complete' || !$img.is('img') ?
imgLoad() : $img.one('load', imgLoad);
/*
* MSIE 9.0 doesn't always fire the image load event -- resetting the src
* attribute seems to trigger it. The check is for version 7 and above to
* accommodate for MSIE 9 running in compatibility mode.
*/
if (!imgLoaded && msie && msie >= 7)
img.src = img.src;
};
/**
* Invoke imgAreaSelect on a jQuery object containing the image(s)
*
* @param options
* Options object
* @return The jQuery object or a reference to imgAreaSelect instance (if the
* <code>instance</code> option was specified)
*/
$.fn.imgAreaSelect = function (options) {
options = options || {};
this.each(function () {
/* Is there already an imgAreaSelect instance bound to this element? */
if ($(this).data('imgAreaSelect')) {
/* Yes there is -- is it supposed to be removed? */
if (options.remove) {
/* Remove the plugin */
$(this).data('imgAreaSelect').remove();
$(this).removeData('imgAreaSelect');
}
else
/* Reset options */
$(this).data('imgAreaSelect').setOptions(options);
}
else if (!options.remove) {
/* No exising instance -- create a new one */
/*
* If neither the "enable" nor the "disable" option is present, add
* "enable" as the default
*/
if (options.enable === undefined && options.disable === undefined)
options.enable = true;
$(this).data('imgAreaSelect', new $.imgAreaSelect(this, options));
}
});
if (options.instance)
/*
* Return the imgAreaSelect instance bound to the first element in the
* set
*/
return $(this).data('imgAreaSelect');
return this;
};
})(jQuery);
أجابت الآلة على الأسئلة ؟: ماذا قال روبوت ل محمد طه؟
بعد 8 سنوات .. تنبؤات الروبوت تتحول إلى واقع..
حروب 2025 في حوار منذ2017؟
: محمد طه يكشف أسرار حول الذكاء الاصطناعي
كتبت : فرح سمير
تضمن حواراً فريدًا من نوعه، BBC في عام 2017، شهد الإعلام العربي حدثًا غير مسبوق، حيث تم بث مباشر عبر قناة العربية
أُجري مع روبوت مدعوم بالذكاء الاصطناعي يحاكي البشر .وكان هذا الحوار من تقديم الإعلامي محمد طه، مراسل الشؤون التكنولوجية في القناة
منذ ذلك الحين، واصل محمد طه تقديم أبرز التقارير والتغطيات لأهم المؤتمرات العالمية، وكان دائمًا مميز ا في تناول أحدث التطورات التكنولوجية وفي هذا اللقاء الحصري، يحدثنا عن كواليس حواره مع الروبوت “صوفيا”، ويتطرق إلى آرائه حول الذكاء الاصطناعي، ومستقبل الإعلام، والتحولات التقنية التي باتت تؤثر في كل القطاعات
– صف لي شعورك عندما أجابك الروبوت، واستطاع بالفعل مجاراة الحوار، نظرًا لأن المقابلة كانت في عام 2017 ؟
في البداية، كان لدي انطباع بأن الروبوت قد لا يكون كائناً مستقلًً، بل ربما يكون هناك شخص ما يتواصل معه أو يدعمه في تقديم الإجابات. لم أكن مقتنعاً في البداية بقدرة الروبوت على الرد على أسئلتي بشكل مستقل دون أي مساعدة من شخص آخر.هذا الأمر لم يكن شائعاً كما هو اليوم.
أثناء تواجدي في معرض، تم إخباري بإمكانية إجراء محادثة مع روبوت، ولم أصدق ذلك. كان انطباعي الأول أن هناك شخصًا يستمع إلى سؤالي ويقوم بالرد عليه بشكل مستقل ودون تدخل بشري. لم أكن أتخيل أن الروبوت قادر على تقديم إجابات مباشرة من مكان ما أو من غرفة تحكم.
– هل قمَتَ بتعديل أسلوبك الصحفي أو الحواري ليتناسب مع قدرة الروبوت على معالجة الأمر والتجاوب معك؟
كان هدفي الأساسي خلال الحوار ه والحفاظ على طبيعية التفاعل، وه وما شكل تحديًا كبيرًا. فعندما تواجه جهازًا توجه له أسئلة ويقوم بالرد عليك، فإن ذلك كان يعتبر في السابق نوعًا من الخيال. نحن في مجال الصحافة والإعلام نملك خبرة واسعة في التعامل مع البشر، حيث يمكنني رؤية تعبيرات وجهك وتفاعل جسدك مما يساعدني على تعديل أسلوبي وفق ذلك. لكن عند التعامل مع الروبوت، تفتقر إلى تعبيرات الوجه والملامح التفاعلية، مما يجعل ردود الفعل غير قابلة للتنب ؤ.
ما الذي لاحظته من فروقات واضحة في طريقة التعامل مع روبوت مقارنة بمقابلة إعلامية طبيعية مع إنسان؟
بالتأكيد هناك اختلافات تتعلق بتحضير المقابلة. عند إجراء مقابلة مع إنسان، يتم إعداد الحوار بناءً على شخصية الضيف وخلفيته. أما بالنسبة للروبوت فلم أكن أملك معلومات مسبقة عنه. كان تركيزي منصبًا على طبيعة الروبوت نفسه وعلاقته بالإنسان ومستقبله.
ذكرت في بداية المراسلة أن ردود أفعالها تشبه البشر، فهل تشبه ردود أفعال الروبوت وتعبير وجهه الإنسان؟ كما ذكرت أثناء تصوير المقابلة أن “الروبوت” المصنوع يحُاكي الأنثى البشرية، وحصل على الجنسية السعودية؟
نعم ذكرت بالفعل جنسيتهاً ولقد ذكرت بالفعل أن للروبوت تعبيرات وجه وحركات معينة تجعل الشخص يتساءل عما إذا كان يتعامل مع كائن بشري أو شبه بشري أو آلة. خلًل المقابلة لم نكن معتادين على هذه التقنية الجديدة.
– من خلال الحوار مع الروبوت، هل ترى أن الاندماج والتعامل مع الروبوتات يُعد تحدياً، أم أنه سهل التأقلم؟ وهل يمثل هذا تحدياً لنا كإعلاميين أو كبشر عمومًا؟
لا أعتقد أنه يمثل تحديًا كبيرًا؛ فقد أصبح الناس معتادين بشكل متزايد على الذكاء الاصطناعي وبدأوا يتأقلمون معه بسهولة. سأعطيك مثالًا آخر: كنتُ في إحدى الدول، وطلبتُ بعض أدوات تنظيف الأسنان، وعندما فتحت الباب، فوجئت بأن روبوت أحضرها لي. لم أستغرب، لأني بالفعل أصبحت أتوقع مثل هذه الأمور .
في وقت الحوار عام 2017، هل واجهت أي صعوبات أو تحديات؟
بالتأكيد كانت هناك حالة من الاستغراب وكان التحدي يكمن في الحفاظ على أسلوب تلقائي وطبيعي رغم أنني كنت أتحدث إلى آلة. كان عليّ أن أتعامل معها كإنسان حقيقي، وأن أطرح عليها أسئلة كما أفعل في مقابلاتي المعتادة، حتى يكون الحوار سلسًا.
– خلال الحوار، طرحتَ سؤا الا: “بماذا ترغبين في العمل في المستقبل؟” فأجابت الروبوت “صوفيا” بإجابة مفاجئة حول التظاهرات الدموية. كيف استقبلت هذه الإجابة؟
كانت إجابتها صادمة للغاية وقد أثارت تساؤلات حول دقة توقعاتها والخوارزميات المستخدمة خلف تلك التحليلات التي صرحتها وكانت “في المستقبل سيكون هناك تظاهرات دموية وصراعات ما لم نصبح أكثر ذكاءً واجتماعية مثلي، وربما نساعد في التعامل مع هذا الوضع.”
في ذلك الوقت، لم تكن هناك مؤشرات على أي حروب أو صراعات، بل كانت الأمور مستقرة نسب ايا. لكن مع مرور السنوات، وقعت فعل ايا أحداث دموية وصراعات كبيرة.
هذا يثير التساؤلات حول دقة توقعات الذكاء الاصطناعي، والخوارزميات التي تقف خلف هذه التحليلات.
هل تعتقد أن توقعاتها كانت مبنية على خوارزميات دقيقة، أم تدخل بشري؟
قد تكون مستندة إلى تحليلات بشرية أ و تقارير صحفية؛ فالروبوت لا يفكر كما نفكر، بل يعتمد على البيانات والخوارزميات المعقدة لتقديم توقعاته المستقبلية. هذا يجعلنا نعود إلى التساؤل: إلى أي مدى تستطيع الروبوتات أن تتعلّم ذاتيًا؟ وهل يمكن أن تتخذ قرارات مستقلة مستقبلاً ؟
هل من الممكن أن تنشئ الروبوتات شركات وتدير أعما الا مستقلة ؟
نعم، كان هناك تقارير أعدتها عن روبوتات قامت بتأسيس شركات خاصة بها وأصبحت تديرها بشكل مستقل تقريب اً باستثناء وجود شخص واحد فقط لدعم العمليات الإدارية.
كنت قد غطيت مؤتمر باريس مؤخ ارا. ما أبرز ما جاء فيه؟
فرنسا نظّمت أكبر قمة عالمية للذكاء الاصطناعي منذ ستة أسابيع، وشاركت فيها شخصيات دولية رفيعة المستوى. شهد المؤتمر دعوات لوضع قوانين أخلاقية لتنظيم تطور الذكاء الاصطناعي بينما رفضت بعض الدول الكبرى مثل الولايات المتحدة وبريطانيا الالتزام بهذه المبادئ. الاستمرار في التطوير دون قيود.
وفي نهاية القمة، صدر “إعلان باريس”، الذي شددّ على أهمية مراعاة الأخلاقيات في تطوير الذكاء الاصطناعي.
–إذا بنا ء على ما تم ذكره، بماذا توصي الصحفيين في الفترة القادمة؟ فيما يلتزمون به أ ويلتفتون إليه؟ وبماذا توصيهم أو ما النصائح التي تنصح الصحفيين أن يتبعوها لمجاراة التقدم المستم ر في التكنولوجيا أو الذكاء الاصطناعي؟
يجب على الصحفيين تعلم استخدام الذكاء الاصطناعي وتطبيقه في كل جوانب عملهم، مثل إعداد الفيديوهات والبحث، مع الالتزام بالأخلًقية في استخدامه. هذه التقنية تسهل مهامهم وتوفر الوقت والجهد، مما يحسن جودة العمل. من الضروري أن يكون الجميع قادرين على تمييز الأعمال التي تنتجها الآلات وعدم الاعتماد عليها كلياً واستخدامها كأداة مساعدة. وانا كصحفي في التلفزيوني أركز على الجانب البصري واستخدم تقنياته الجرافيكية والتوضيحية الخاصة به عند الظهور في التلفزيون، ولا يعتمد على غيره في إنتاج عمله بفضل تعليمه وخبرته وفريقه الذي يساعده في إنتاج محتوى بصري قوي ومميز. ورغم تطور الذكاء الاصطناعي، فإنه لا يزال غير قادر على إنتاج أعمال تجمع بين العناصر البصرية والشخصية الفريدة للصحفي أو المراسل، ويخدم الصحفي فقط في مجال محدد. يجب على الصحفيين بناء شخصية وأسلوب وحضور خاص بهم ودمجها مع تقنياتهم ومعارفهم، وتوظيف الذكاء الاصطناعي بما يتناسب مع أسلوبهم وحضورهم لتحقيق فائدة أكبر. ينبغي على الصحفيين التفكير في القيمة المضافة والتركيز على الجودة لا الكمية، فالأهم هو محتوى وأهمية ما يقدم للجمهور لا عدد الأخبار. الكاتب يوجه تركيزه نحو إعداد تقرير مصور أسبوعي أو شهري شامل وعالي الجودة لجذب الجمهور. لذا، من الضروري أن يوجه الصحفيون اهتمامهم نحو تقديم محتوى عالي الجودة بدلاً من التركيز على الكم فقط.
-عند لمس الروبوت، هل كان هنا ك أي اختلاف ب ي ملمس جلد الإنسان وجلده؟
بالنسبة للمس بين جلد الإنسان وجلد الروبوت، لا يوجد فرق كبير. ومع ذلك، يتردد الناس في لمس الروبوت بسبب حساسية. الموضوع وارتباط ه بحقو ق الروبوت ككائن إنساني. وكان من الضروري مراعاة جميع الثقافات المختلفة ف ي هذا السياق.
بما أن الحوار أُجري منذ ٨ أعوام ف ٢٠١٧، فما هى التطورات التى حدثت للروبوتات بشكل عا م خلال السنوات الثمان الماضية من وجهة نظرك ؟
نحن على أعتاب عقد جديد حيث يصعب تصور تأثير التكنولوجيا على مجالات مثل الإعلًم والطب والاقتصاد. جميع المؤسسات تفكر بجدية في مستقبل موظفيها وأنظمتها، مما يتطلب استعداًدًا كبيرًا للتغييرات المرتقبة. هناك شخصيات بارزة مثل إيلون ماسك تعبر عن أهمية استكشاف المريخ، وتدور نقاشات بينه وبين مؤسس وكالة ناسا حول أولويات الرحلًت الفضائية. يؤكد ماسك على ضرورة إنشاء قاعدة على المريخ نظرًا للتغيرات التي نشهدها على كوكب الأرض.
وفي نهاية الحوار، وبعد شكر ك له، و جه إليك سؤا لا بصيغة تح د وأمر، حيث قال” :قبل أن تمضي، ما هي توقعاتك للمستقبل؟”، فهل شعر ت حينها بالخطورة من تح ديه للعقل البشري؟ وهل توقعت أنه سيأتي يو م يكو ن فيه هو من يوجه الأسئلة ويغطي الأخبار ويحل محل الإعلامي؟
هذا السؤال جعلني أشعر بالراحة في التعامل مع الروبوت، حيث كان الحوار تفاعلياً وحقيقيًا .لم أكن أتوقع يومًا أن يأتي وقت يكون فيه روبوت هو من يسأل ويخطط الأخبار، بل ويحاكي الإعلام بشكل عام .التكنولوجيا اليوم تفوق أي توقعات سابقة .قبل بضع .سنوات فقط، لم يكن هناك تصو ر لوجود روبوتات قادرة على التعامل مع الأخبار بنفس كفاءة البشر.
من خلال خبرتك في المجال الإعلامي، هل سيقبل الجمهور بفكرة وجود مذيع برامج أو مراسل أو صحفي انه آلة ( روبوت)؟ هل هناك تجارب سابقة من بي بي سي تشير إلى تقبل الجمهور لهذا الأمر ؟
لا تزال مؤسسة البي بي سي تقوم بتجارب لفهم مدى تفاعل الجمهور واستجابته لهذه الآلات أو المذيعين الآليين قبل تنفيذها بشكل كامل. ستقوم البي بي سي بإجراء بحوث للجمهور لفهم كيفية تفاعله م مع الروبوتات والذكاء الاصطناعي ومقارنتها بالتفاعل مع الأشخاص العاديين. بالنظر إلى المستقبل، قد تتطور الروبوتات لتصبح أكثر ذكاءً وقدرة ً على التكيف مع احتياجات الجمهور
بناءا على تقنية (Machine Learning)) نظرية التعلم الآلي: يرى إيلون ماسك أن نظرية التعلم الآلي قد تؤدي إلى تطور الروبوتات بشكل مستقل وقدرتها على التحكم بالبشر، وهو ما يظهر في أفلًم الذكاء الاصطناعي كمصدر للمخاوف والتحديات. يوجد خلًف بين ماسك وزوكربيرج حول هذا الاحتمال، حيث يعتبر زوكربيرج مخاوف ماسك مبالغًا فيها، بينما يشدد ماسك على أهمية الاستعداد لهذا السيناريو بحذر. في الوقت نفسه، تكشف الدراسات عن تطور علًقات عاطفية بين البشر والشات بوتس، وهي ظاهرة بدأت في الغرب وتظهر في الشرق الأوسط، مما يثير تساؤلات حول طبيعة العلًقة المستقبلية بين الإنسان والآلة وتأثيرها على مفاهيم السلطة والسيطرة.
في ختام هذا الحور المثير مع الإعلامي “محمد طه” ، نرى بوضوح كيف بدأ مشهد الإعلام والتقنية في التغي ر جذرياً منذ عام 2017 مع أول حوار تلفزيوني مع روبوت. تجربة طه مع “صوفيا” لم تكن مجرد حدث إعلامي فريد، ب ل كانت نافذة مبكرة على مستقبل يتقاطع فيه الإنسان والآلة بشكل متزايد.