]> www.vanbest.org Git - motioneye-debian/commitdiff
added a mask grid overlaid on top of camera frames
authorCalin Crisan <ccrisan@gmail.com>
Wed, 14 Sep 2016 19:17:31 +0000 (22:17 +0300)
committerCalin Crisan <ccrisan@gmail.com>
Wed, 14 Sep 2016 19:17:31 +0000 (22:17 +0300)
motioneye/config.py
motioneye/handlers.py
motioneye/static/css/main.css
motioneye/static/js/main.js
motioneye/templates/main.html
motioneye/utils.py

index 11ee4a505d4c4f9218714612dd494e05d25e2494..413101c3ad03fb5af9821d94d7a870a0cfa8096a 100644 (file)
@@ -869,12 +869,12 @@ def motion_camera_ui_to_dict(ui, old_config=None):
     data['ffmpeg_variable_bitrate'] = int(vbr)
 
     # motion detection
-    if ui['mask']:
+    if ui['mask'] and data.get('width') and data.get('height'):
         if ui['mask_type'] == 'smart':
             data['smart_mask_speed'] = 10 - int(ui['smart_mask_slugginess'])
 
         elif ui['mask_type'] == 'editable':
-            data['mask_file'] = utils.build_editable_mask_file(ui['editable_mask'])
+            data['mask_file'] = utils.build_editable_mask_file(old_config['@id'], data['width'], data['height'], ui['mask_lines'])
 
     # working schedule
     if ui['working_schedule']:
@@ -1058,7 +1058,7 @@ def motion_camera_dict_to_ui(data):
         'mask': False,
         'mask_type': 'smart',
         'smart_mask_slugginess': 5,
-        'editable_mask': '',
+        'mask_lines': [],
         
         # motion notifications
         'email_notifications_enabled': False,
@@ -1245,10 +1245,10 @@ def motion_camera_dict_to_ui(data):
     ui['movie_quality'] = int(q)
 
     # mask
-    if data['mask_file']:
+    if data['mask_file'] and data.get('width') and data.get('height'):
         ui['mask'] = True
         ui['mask_type'] = 'editable'
-        ui['editable_mask'] = utils.parse_editable_mask_file(data['mask_file'])
+        ui['mask_lines'] = utils.parse_editable_mask_file(data['@id'], data['width'], data['height'])
 
     elif data['smart_mask_speed']:
         ui['mask'] = True
index 448686187c1e2f4c385ba8dee1d9f4d169b423a3..85ea8d09b199994aa501400e609619a5340980cc 100644 (file)
@@ -207,8 +207,9 @@ class MainHandler(BaseHandler):
                 admin_username=config.get_main().get('@admin_username'),
                 has_streaming_auth=motionctl.has_streaming_auth(),
                 has_new_movie_format_support=motionctl.has_new_movie_format_support(),
-                has_motion=bool(motionctl.find_motion()))
-    
+                has_motion=bool(motionctl.find_motion()),
+                mask_width=utils.MASK_WIDTH)
+
 
 class ConfigHandler(BaseHandler):
     @asynchronous
index 191f167dd631a4dbde51f6a0bae07c6677176a5a..1c7e6f3b0f4623b507746a24c7188f807e29e5eb 100644 (file)
@@ -903,6 +903,7 @@ div.camera-overlay-bottom {
     transition: background-color 0.1s ease;
     left: 0px;
     width: 100%;
+    z-index: 1;
 }
 
 div.camera-frame:HOVER div.camera-overlay-top,
@@ -922,6 +923,46 @@ div.camera-overlay-top {
     white-space: nowrap;
 }
 
+div.camera-overlay-mask {
+    background: #888;
+    opacity: 0;
+    position: absolute;
+    top: 0px;
+    right: 0px;
+    bottom: 0px;
+    left: 0px;
+    z-index: 0;
+}
+
+div.camera-overlay.mask-edit > div.camera-overlay-top,
+div.camera-overlay.mask-edit > div.camera-overlay-bottom {
+    display: none;
+}
+
+div.camera-overlay.mask-edit > div.camera-overlay-mask {
+    opacity: 0.3;
+}
+
+div.mask-element {
+    position: absolute;
+    box-sizing: border-box;
+    border: 1px solid #aaa;
+    border-width: 0px 1px 1px 0px;
+    background: transparent;
+}
+
+div.mask-element.last-row {
+    border-right-width: 0px;
+}
+
+div.mask-element.last-line {
+    border-bottom-width: 0px;
+}
+
+div.mask-element.on {
+    background: #444;
+}
+
 div.camera-name {
     display: inline-block;
     font-size: 1.2em;
index 8a7f79d8b1ac13bef430d0d903fd365d5c179905..095f37d788599d6e2c07af523493104923eacfdd 100644 (file)
@@ -978,6 +978,92 @@ function hideCameraOverlay() {
     }, 300);
 }
 
+function enableMaskEdit(cameraId, width, height) {
+    var cameraFrame = getCameraFrame(cameraId);
+    var overlayDiv = cameraFrame.find('div.camera-overlay');
+    var maskDiv = cameraFrame.find('div.camera-overlay-mask');
+    
+    overlayDiv.addClass('mask-edit');
+
+    var nx = maskWidth; /* number of rectangles */
+    var rx, rw;
+    if (width % nx) {
+        nx--;
+        rx = width % nx; /* remainder */
+    }
+    else {
+        rx = 0;
+    }
+    
+    rw = parseInt(width / nx); /* rectangle width */
+
+    var maskHeight;
+    var ny = maskHeight = parseInt(height * maskWidth / width); /* number of rectangles */
+    var ry, rh;
+    if (height % ny) {
+        ny--;
+        ry = height % ny; /* remainder */
+    }
+    else {
+        ry = 0;
+    }
+    
+    rh = parseInt(height / ny); /* rectangle height */
+    
+    function makeMaskElement(x, y, px, py, pw, ph) {
+        px = px * 100 / width;
+        py = py * 100 / height;
+        pw = pw * 100 / width;
+        ph = ph * 100 / height;
+
+        var el = $('<div class="mask-element"></div>');
+        el.css('left', px + '%');
+        el.css('top', py + '%');
+        el.css('width', pw + '%');
+        el.css('height', ph + '%');
+        if (x == maskWidth - 1) {
+            el.addClass('last-row');
+        }
+        if (y == maskHeight - 1) {
+            el.addClass('last-line');
+        }
+        maskDiv.append(el);
+    }
+    
+    /* make sure the mask is empty */
+    maskDiv.html('');
+
+    var x, y;
+    for (y = 0; y < ny; y++) {
+        for (x = 0; x < nx; x++) {
+            makeMaskElement(x, y, x * rw, y * rh, rw, rh);
+        }
+
+        if (rx) {
+            makeMaskElement(x, y, nx * rw, y * rh, rx, rh);
+        }
+    }
+
+    if (ry) {
+        for (x = 0; x < nx; x++) {
+            makeMaskElement(x, y, x * rw, ny * rh, rw, ry);
+        }
+
+        if (rx) {
+            makeMaskElement(x, y, nx * rw, ny * rh, rx, ry);
+        }
+    }
+}
+
+function disableMaskEdit(cameraId) {
+    var cameraFrame = getCameraFrame(cameraId);
+    var overlayDiv = cameraFrame.find('div.camera-overlay');
+    var maskDiv = cameraFrame.find('div.camera-overlay-mask');
+    
+    overlayDiv.removeClass('mask-edit');
+    maskDiv.html('');
+}
+
 
     /* settings */
 
@@ -1557,7 +1643,7 @@ function cameraUi2Dict() {
         'mask': $('#maskSwitch')[0].checked,
         'mask_type': $('#maskTypeSelect').val(),
         'smart_mask_slugginess': $('#smartMaskSlugginessSlider').val(),
-        'mask_file': $('#maskFileEntry').val(),
+        'mask_lines': [], // TODO generate mask lines
         
         /* motion notifications */
         'email_notifications_enabled': $('#emailNotificationsEnabledSwitch')[0].checked,
@@ -1901,7 +1987,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');
-    $('#maskFileEntry').val(dict['mask_file']); markHideIfNull('mask_file', 'maskFileEntry');
+    //TODO use dict['mask_lines']; markHideIfNull('mask_file', 'maskFileEntry');
     
     /* motion notifications */
     $('#emailNotificationsEnabledSwitch')[0].checked = dict['email_notifications_enabled']; markHideIfNull('email_notifications_enabled', 'emailNotificationsEnabledSwitch');
@@ -3973,6 +4059,7 @@ function addCameraFrameUi(cameraConfig) {
                             '<div class="button icon camera-top-button mouse-effect configure" title="configure this camera"></div>' +
                         '</div>' +
                     '</div>' +
+                    '<div class="camera-overlay-mask"></div>' +
                     '<div class="camera-overlay-bottom">' +
                         '<div class="camera-info">' +
                             '<span class="camera-info" title="streaming/capture frame rate"></span>' +
index 9b579e33d9621d12c374d5148a47796881306a03..c339ab4861b54accfdaf46e21e68ffcf1ba9711b 100644 (file)
@@ -69,6 +69,7 @@
         var frame = {% if frame %}true{% else %}false{% endif %};
         var hasLocalCamSupport = {% if has_motion %}true{% else %}false{% endif %};
         var hasNetCamSupport = {% if has_motion %}true{% else %}false{% endif %};
+        {% if mask_width %}var maskWidth = {{mask_width}};{% endif %}
     </script>
 {% endblock %}
 
index 84d75ebf1deec30e75afa9468af4db7f711c3db3..6aafcdf31ee149d5764fd700c2b7a91391c11c7a 100644 (file)
@@ -47,7 +47,7 @@ except:
 _SIGNATURE_REGEX = re.compile('[^a-zA-Z0-9/?_.=&{}\[\]":, _-]')
 _SPECIAL_COOKIE_NAMES = {'expires', 'domain', 'path', 'secure', 'httponly'}
 
-_MASK_WIDTH = 32
+MASK_WIDTH = 32
 
 DEV_NULL = open('/dev/null', 'w')
 
@@ -788,18 +788,26 @@ def urlopen(*args, **kwargs):
 
 def build_editable_mask_file(camera_id, width, height, mask_lines):
     # horizontal rectangles
-    nx = _MASK_WIDTH    # number of rectangles
-    rw = width / nx     # rectangle width
-    rx = width % nx     # remainder
-    if rx:
+    nx = MASK_WIDTH # number of rectangles
+    if width % nx:
         nx -= 1
+        rx = width % nx # remainder
+
+    else:
+        rx = 0
+    
+    rw = width / nx # rectangle width
 
     # vertical rectangles
-    ny = height * _MASK_WIDTH / width   # number of rectangles
-    rh = height / ny                    # rectangle height
-    ry = height % ny                    # remainder
-    if ry:
+    ny = height * MASK_WIDTH / width # number of rectangles
+    if height % ny:
         ny -= 1
+        ry = height % ny # remainder
+    
+    else:
+        ry = 0
+
+    rh = height / ny # rectangle height
 
     # draw the actual mask image content
     im = Image.new('L', (width, height), 255) # all white
@@ -808,20 +816,20 @@ def build_editable_mask_file(camera_id, width, height, mask_lines):
     for y in xrange(ny):
         line = mask_lines[y]
         for x in xrange(nx):
-            if line & (_MASK_WIDTH - 1 - x):
-                dr.rectangle((x * rw, y * rh, (x + 1) * rw, (y + 1) * rh), fill=0)
+            if line & (1 << (MASK_WIDTH - 1 - x)):
+                dr.rectangle((x * rw, y * rh, (x + 1) * rw - 1, (y + 1) * rh - 1), fill=0)
 
-        if rx and line & (nx + 1):
-            dr.rectangle((nx * rw, y * rh, nx * rw + rx, (y + 1) * rh), fill=0)
+        if rx and line & 1:
+            dr.rectangle((nx * rw, y * rh, nx * rw + rx - 1, (y + 1) * rh - 1), fill=0)
 
     if ry:
         line = mask_lines[ny]
         for x in xrange(nx):
-            if line & (_MASK_WIDTH - 1 - x):
-                dr.rectangle((x * rw, ny * rh, (x + 1) * rw, ny * rh + ry), fill=0)
+            if line & (1 << (MASK_WIDTH - 1 - x)):
+                dr.rectangle((x * rw, ny * rh, (x + 1) * rw - 1, ny * rh + ry - 1), fill=0)
 
-        if rx and line & (nx + 1):
-            dr.rectangle((nx * rw, ny * rh, nx * rw + rx, ny * rh + ry), fill=0)
+        if rx and line & 1:
+            dr.rectangle((nx * rw, ny * rh, nx * rw + rx - 1, ny * rh + ry - 1), fill=0)
 
     file_name = os.path.join(settings.CONF_PATH, 'mask_%s.pgm' % camera_id)
     im.save(file_name, 'ppm')
@@ -832,18 +840,26 @@ def parse_editable_mask_file(camera_id, width, height):
     # as it might be different from that of the associated mask
 
     # horizontal rectangles
-    nx = _MASK_WIDTH    # number of rectangles
-    rw = width / nx     # rectangle width
-    rx = width % nx     # remainder
-    if rx:
+    nx = MASK_WIDTH # number of rectangles
+    if width % nx:
         nx -= 1
+        rx = width % nx # remainder
+
+    else:
+        rx = 0
+    
+    rw = width / nx # rectangle width
 
     # vertical rectangles
-    ny = mask_height = height * _MASK_WIDTH / width   # number of rectangles
-    rh = height / ny                    # rectangle height
-    ry = height % ny                    # remainder
-    if ry:
+    ny = mask_height = height * MASK_WIDTH / width # number of rectangles
+    if height % ny:
         ny -= 1
+        ry = height % ny # remainder
+    
+    else:
+        ry = 0
+
+    rh = height / ny # rectangle height
 
     file_name = os.path.join(settings.CONF_PATH, 'mask_%s.pgm' % camera_id)
 
@@ -855,7 +871,7 @@ def parse_editable_mask_file(camera_id, width, height):
         logging.error('failed to read mask file %s: %s' % (file_name, e))
 
         # empty mask        
-        return [2 ** _MASK_WIDTH - 1] * mask_height
+        return [2 ** MASK_WIDTH - 1] * mask_height
     
     # resize the image if necessary
     if im.size != (width, height):
@@ -868,48 +884,44 @@ def parse_editable_mask_file(camera_id, width, height):
     for y in xrange(ny):
         bits = []
         for x in xrange(nx):
-            px = (x + 0.5) * rw
-            py = (y + 0.5) * rh
-            pixel = pixels[py * height + px]
-            if pixel == 0:
-                bits.append(not bool(pixel))
+            px = int((x + 0.5) * rw)
+            py = int((y + 0.5) * rh)
+            pixel = pixels[py * width + px]
+            bits.append(not bool(pixel))
 
         if rx:
-            px = nx * rw + rx / 2
-            py = (y + 0.5) * rh
-            pixel = pixels[py * height + px]
-            if pixel == 0:
-                bits.append(not bool(pixel))
+            px = int(nx * rw + rx / 2)
+            py = int((y + 0.5) * rh)
+            pixel = pixels[py * width + px]
+            bits.append(not bool(pixel))
 
         # build the binary packed mask line
         line = 0
         for i, bit in enumerate(bits):
             if bit:
-                line |= 1 << (_MASK_WIDTH - 1 - i)
+                line |= 1 << (MASK_WIDTH - 1 - i)
                 
         mask_lines.append(line)
 
     if ry:
         bits = []
         for x in xrange(nx):
-            px = (x + 0.5) * rw
-            py = ny * rh + ry / 2
-            pixel = pixels[py * height + px]
-            if pixel == 0:
-                bits.append(not bool(pixel))
+            px = int((x + 0.5) * rw)
+            py = int(ny * rh + ry / 2)
+            pixel = pixels[py * width + px]
+            bits.append(not bool(pixel))
 
         if rx:
-            px = nx * rw + rx / 2
-            py = ny * rh + ry / 2
-            pixel = pixels[py * height + px]
-            if pixel == 0:
-                bits.append(not bool(pixel))
+            px = int(nx * rw + rx / 2)
+            py = int(ny * rh + ry / 2)
+            pixel = pixels[py * width + px]
+            bits.append(not bool(pixel))
 
         # build the binary packed mask line
         line = 0
         for i, bit in enumerate(bits):
             if bit:
-                line |= 1 << (_MASK_WIDTH - 1 - i)
+                line |= 1 << (MASK_WIDTH - 1 - i)
 
         mask_lines.append(line)