From: Calin Crisan Date: Sat, 17 Sep 2016 18:34:14 +0000 (+0300) Subject: mask editing should now be fully functional X-Git-Url: http://www.vanbest.org/gitweb/?a=commitdiff_plain;h=0506aecb9f9b6cd12f5adb65928c87783e88c225;p=motioneye-debian mask editing should now be fully functional --- diff --git a/motioneye/static/css/main.css b/motioneye/static/css/main.css index d5621d7..4803417 100644 --- a/motioneye/static/css/main.css +++ b/motioneye/static/css/main.css @@ -476,7 +476,7 @@ input[type=text].working-schedule.number { width: 90%; } -#editableMaskEntry { +#maskLinesEntry { display: none; } diff --git a/motioneye/static/js/main.js b/motioneye/static/js/main.js index e2e475d..c9871a9 100644 --- a/motioneye/static/js/main.js +++ b/motioneye/static/js/main.js @@ -811,6 +811,50 @@ function initUI() { $('#saturationSlider').change(function () {pushPreview('saturation');}); $('#hueSlider').change(function () {pushPreview('hue');}); + /* whenever the window is resized, + * if a modal dialog is visible, it should be repositioned */ + $(window).resize(updateModalDialogPosition); + + /* autoselect urls in read-only entries */ + $('#streamingSnapshotUrlEntry:text, #streamingMjpgUrlEntry:text, #streamingEmbedUrlEntry:text').click(function () { + this.select(); + }); + + /* show a warning when enabling media files removal */ + var preserveSelects = $('#preservePicturesSelect, #preserveMoviesSelect'); + var rootDirectoryEntry = $('#rootDirectoryEntry'); + preserveSelects.focus(function () { + this._prevValue = $(this).val(); + }).change(function () { + var value = $(this).val(); + if (value != '0' && this._prevValue == '0') { + var rootDir = rootDirectoryEntry.val(); + runAlertDialog(('This will recursively remove all old media files present in the directory "' + rootDir + + '", not just those created by motionEye!')); + } + }); + + /* disable mask editor when mask gets disabled */ + $('#maskSwitch').change(function () { + if (!this.checked) { + disableMaskEdit(); + } + }); + + /* disable mask editor when mask gets disabled */ + $('#maskSwitch').change(function () { + if (!this.checked) { + disableMaskEdit(); + } + }); + + /* disable mask editor when mask type is no longer editable */ + $('#maskTypeSelect').change(function () { + if ($(this).val() != 'editable') { + disableMaskEdit(); + } + }); + /* apply button */ $('#applyButton').click(function () { if ($(this).hasClass('progress')) { @@ -830,34 +874,60 @@ function initUI() { doReboot(); }); - /* whenever the window is resized, - * if a modal dialog is visible, it should be repositioned */ - $(window).resize(updateModalDialogPosition); - /* remove camera button */ $('div.button.rem-camera-button').click(doRemCamera); /* logout button */ $('div.button.logout-button').click(doLogout); - /* autoselect urls in read-only entries */ - $('#streamingSnapshotUrlEntry:text, #streamingMjpgUrlEntry:text, #streamingEmbedUrlEntry:text').click(function () { - this.select(); - }); - - /* show a warning when enabling media files removal */ - var preserveSelects = $('#preservePicturesSelect, #preserveMoviesSelect'); - var rootDirectoryEntry = $('#rootDirectoryEntry'); - preserveSelects.focus(function () { - this._prevValue = $(this).val(); - }).change(function () { - var value = $(this).val(); - if (value != '0' && this._prevValue == '0') { - var rootDir = rootDirectoryEntry.val(); - runAlertDialog(('This will recursively remove all old media files present in the directory "' + rootDir + - '", not just those created by motionEye!')); + /* software update button */ + $('div#updateButton').click(doUpdate); + + /* backup/restore */ + $('div#backupButton').click(doBackup); + $('div#restoreButton').click(doRestore); + + /* test buttons */ + $('div#uploadTestButton').click(doTestUpload); + $('div#emailTestButton').click(doTestEmail); + $('div#networkShareTestButton').click(doTestNetworkShare); + + /* mask editor buttons */ + $('div#editMaskButton').click(function () { + var cameraId = $('#cameraSelect').val(); + var resolution = $('#resolutionSelect').val(); + if (!cameraId) { + return; } + + if (!resolution) { + /* + * motion requires the mask file to be the same size as the + * captured images; however for netcams we have no means to know in + * advance the size of the stream; therefore, for netcams, we impose + * here a standard fixed mask size, which WILL NOT WORK for netcam + * streams of a different resolution, unless motion is patched accordingly + */ + resolution = maskDefaultResolution; + } + + resolution = resolution.split('x'); + var width = resolution[0]; + var height = resolution[1]; + + enableMaskEdit(cameraId, width, height); }); + $('div#saveMaskButton').click(function () { + disableMaskEdit(); + }); + $('div#clearMaskButton').click(function () { + var cameraId = $('#cameraSelect').val(); + if (!cameraId) { + return; + } + + clearMask(cameraId); + }); } function getPageContainer() { @@ -989,7 +1059,7 @@ function enableMaskEdit(cameraId, width, height) { var cameraFrame = getCameraFrame(cameraId); var overlayDiv = cameraFrame.find('div.camera-overlay'); var maskDiv = cameraFrame.find('div.camera-overlay-mask'); - + if (overlayDiv.hasClass('mask-edit')) { return; /* already enabled */ } @@ -1023,10 +1093,59 @@ function enableMaskEdit(cameraId, width, height) { var mouseDown = false; var currentState = false; + var elementsMatrix = Array.apply(null, Array(maskHeight)).map(function(){return []}); + + function matrixToMaskLines() { + var maskLines = []; + var bits, line; + + for (y = 0; y < ny; y++) { + bits = []; + for (x = 0; x < nx; x++) { + bits.push(elementsMatrix[y][x].hasClass('on')); + } + + if (rx) { + bits.push(elementsMatrix[y][nx].hasClass('on')); + } + + line = 0; + bits.forEach(function (bit, i) { + if (bit) { + line |= 1 << (maskWidth - 1 - i); + } + }); + + maskLines.push(line); + } + + if (ry) { + bits = []; + for (x = 0; x < nx; x++) { + bits.push(elementsMatrix[ny][x].hasClass('on')); + } + + if (rx) { + bits.push(elementsMatrix[ny][nx].hasClass('on')); + } + + line = 0; + bits.forEach(function (bit, i) { + if (bit) { + line |= 1 << (maskWidth - 1 - i); + } + }); + } + + maskLines.push(line); + + $('#maskLinesEntry').val(maskLines.join(',')).change(); + } function handleMouseUp() { mouseDown = false; $('html').unbind('mouseup', handleMouseUp); + matrixToMaskLines(); } function makeMaskElement(x, y, px, py, pw, ph) { @@ -1047,6 +1166,8 @@ function enableMaskEdit(cameraId, width, height) { el.addClass('last-line'); } maskDiv.append(el); + + elementsMatrix[y][x] = el; el.mousedown(function () { mouseDown = true; @@ -1063,6 +1184,8 @@ function enableMaskEdit(cameraId, width, height) { el.toggleClass('on', currentState); }); } + + maskDiv[0]._matrixToMaskLines = matrixToMaskLines; /* make sure the mask is empty */ maskDiv.html(''); @@ -1079,17 +1202,46 @@ function enableMaskEdit(cameraId, width, height) { } if (rx) { - makeMaskElement(x, y, nx * rw, y * rh, rx, rh); + makeMaskElement(nx, y, nx * rw, y * rh, rx, rh); } } if (ry) { for (x = 0; x < nx; x++) { - makeMaskElement(x, y, x * rw, ny * rh, rw, ry); + makeMaskElement(x, ny, x * rw, ny * rh, rw, ry); } if (rx) { - makeMaskElement(x, y, nx * rw, ny * rh, rx, ry); + makeMaskElement(nx, ny, nx * rw, ny * rh, rx, ry); + } + } + + /* use mask lines to initialize the element matrix */ + var line; + var maskLines = $('#maskLinesEntry').val() ? $('#maskLinesEntry').val().split(',').map(function (v) {return parseInt(v);}) : []; + + for (y = 0; y < ny; y++) { + line = maskLines[y]; + for (x = 0; x < nx; x++) { + if (line & (1 << (maskWidth - 1 - x))) { + elementsMatrix[y][x].addClass('on'); + } + } + if (rx && (line & 1)) { + elementsMatrix[y][nx].addClass('on'); + } + } + + if (ry) { + line = maskLines[ny]; + for (x = 0; x < nx; x++) { + if (line & (1 << (maskWidth - 1 - x))) { + elementsMatrix[ny][x].addClass('on'); + } + } + + if (rx && (line & 1)) { + elementsMatrix[ny][nx].addClass('on'); } } @@ -1134,6 +1286,7 @@ function clearMask(cameraId) { var maskDiv = cameraFrame.find('div.camera-overlay-mask'); maskDiv.find('div.mask-element').removeClass('on'); + maskDiv[0]._matrixToMaskLines(); } @@ -1715,7 +1868,7 @@ function cameraUi2Dict() { 'mask': $('#maskSwitch')[0].checked, 'mask_type': $('#maskTypeSelect').val(), 'smart_mask_slugginess': $('#smartMaskSlugginessSlider').val(), - 'mask_lines': $('#maskLinesEntry').val().split(','), + 'mask_lines': $('#maskLinesEntry').val() ? $('#maskLinesEntry').val().split(',').map(function (l) {return parseInt(l);}) : [], /* motion notifications */ 'email_notifications_enabled': $('#emailNotificationsEnabledSwitch')[0].checked, @@ -2059,7 +2212,7 @@ function dict2CameraUi(dict) { $('#maskSwitch')[0].checked = dict['mask']; markHideIfNull('mask', 'maskSwitch'); $('#maskTypeSelect').val(dict['mask_type']); markHideIfNull('mask_type', 'maskTypeSelect'); $('#smartMaskSlugginessSlider').val(dict['smart_mask_slugginess']); markHideIfNull('smart_mask_slugginess', 'smartMaskSlugginessSlider'); - $('#maskLinesEntry').val((dict['mask_lines'] or []).join(',')); markHideIfNull('mask_lines', 'maskLinesEntry'); + $('#maskLinesEntry').val((dict['mask_lines'] || []).join(',')); markHideIfNull('mask_lines', 'maskLinesEntry'); /* motion notifications */ $('#emailNotificationsEnabledSwitch')[0].checked = dict['email_notifications_enabled']; markHideIfNull('email_notifications_enabled', 'emailNotificationsEnabledSwitch'); @@ -4761,55 +4914,6 @@ $(document).ready(function () { } }); - /* software update button */ - $('div#updateButton').click(doUpdate); - - /* backup/restore */ - $('div#backupButton').click(doBackup); - $('div#restoreButton').click(doRestore); - - /* test buttons */ - $('div#uploadTestButton').click(doTestUpload); - $('div#emailTestButton').click(doTestEmail); - $('div#networkShareTestButton').click(doTestNetworkShare); - - /* mask editor buttons */ - $('div#editMaskButton').click(function () { - var cameraId = $('#cameraSelect').val(); - var resolution = $('#resolutionSelect').val(); - if (!cameraId) { - return; - } - - if (!resolution) { - /* - * TODO motion requires the mask file to be the same size as the - * captured images; however for netcams we have no means to know in - * advance the size of the stream; therefore, for netcams, we impose - * here a standard fixed mask size, which WILL NOT WORK for netcam - * streams of a different resolution - */ - resolution = maskDefaultResolution; - } - - resolution = resolution.split('x'); - var width = resolution[0]; - var height = resolution[1]; - - enableMaskEdit(cameraId, width, height); - }); - $('div#saveMaskButton').click(function () { - disableMaskEdit(); - }); - $('div#clearMaskButton').click(function () { - var cameraId = $('#cameraSelect').val(); - if (!cameraId) { - return; - } - - clearMask(cameraId); - }); - initUI(); beginProgress(); diff --git a/motioneye/templates/main.html b/motioneye/templates/main.html index 28a679a..8192b73 100644 --- a/motioneye/templates/main.html +++ b/motioneye/templates/main.html @@ -844,7 +844,7 @@
Edit Mask
Save Mask
- + ? diff --git a/motioneye/utils.py b/motioneye/utils.py index b14e6e7..fbeb091 100644 --- a/motioneye/utils.py +++ b/motioneye/utils.py @@ -814,18 +814,26 @@ def build_editable_mask_file(camera_id, width, height, mask_lines): else: ry = 0 + # if mask not present, generate an empty mask + if not mask_lines: + mask_lines = [0] * mask_height + + # scale the mask vertically in case the aspect ratio has changed + # since the last time the mask has been generated + if mask_height == len(mask_lines): + line_index_func = lambda y: y + + else: + line_index_func = lambda y: (len(mask_lines) - 1) * y / (mask_height - 1) + rh = height / ny # rectangle height # draw the actual mask image content im = Image.new('L', (width, height), 255) # all white dr = ImageDraw.Draw(im) - while len(mask_lines) < mask_height: - # add empty mask lines until mask is tall enough - mask_lines.append(0) - for y in xrange(ny): - line = mask_lines[y] + line = mask_lines[line_index_func(y)] for x in xrange(nx): if line & (1 << (MASK_WIDTH - 1 - x)): dr.rectangle((x * rw, y * rh, (x + 1) * rw - 1, (y + 1) * rh - 1), fill=0) @@ -834,7 +842,7 @@ def build_editable_mask_file(camera_id, width, height, mask_lines): dr.rectangle((nx * rw, y * rh, nx * rw + rx - 1, (y + 1) * rh - 1), fill=0) if ry: - line = mask_lines[ny] + line = mask_lines[line_index_func(ny)] for x in xrange(nx): if line & (1 << (MASK_WIDTH - 1 - x)): dr.rectangle((x * rw, ny * rh, (x + 1) * rw - 1, ny * rh + ry - 1), fill=0)