Initial commit

pull/2/head
ilian.iliev 2023-05-10 21:17:32 +03:00
commit 06a290f0e7
48 changed files with 62102 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.idea
__pycache__/
/outputs/

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Imperia Online JSC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

46
README.md Normal file
View File

@ -0,0 +1,46 @@
<img alt="" src="https://img.shields.io/badge/Python-FFD43B?style=for-the-badge&logo=python&logoColor=blue" />
<img alt="" src="https://img.shields.io/badge/PyTorch-EE4C2C?style=for-the-badge&logo=pytorch&logoColor=white" />
<img alt="" src="https://img.shields.io/badge/Numpy-777BB4?style=for-the-badge&logo=numpy&logoColor=white" />
# eyemask
This extension is for AUTOMATIC1111's [Stable Diffusion web UI](https://github.com/AUTOMATIC1111/stable-diffusion-webui)
### Capabilities
* Create mask for eyes / face / body and redraw by multiple params
* Wildcards for mask prompt and original prompt
* Using of different model just for masked area redraw
* Mask padding in px or percents
* Mask previews
* Separate embedded version of the script (enabled from settings)
* Batch mode support
* Custom API routes (including serving static files)
Put all wildcards files in */wildcards* dir.
### Install
Use *Install from URL* option with this repo url.
### Requirements
All requirements will be installed on first use, except for:
- [cmake](https://cmake.org/download/) - used only for dlib masks
### Mask types
1. Eyes dlib
2. Face dlib
3. Face depthmask
4. Body depthmask
5. Face mmdet
6. Body mmdet
### Examples
<img width="1024" src="https://bitbucket.imperiaonline.org/projects/OPAI/repos/eyemask/raw/static/images/mask-types.jpg" alt="">
### Contributing
Feel free to submit PRs to develop!

60
install.py Normal file
View File

@ -0,0 +1,60 @@
import os
import sys
from launch import is_installed, run, git_clone
from modules.paths import models_path
from modules.sd_models import model_hash
from modules import modelloader
from basicsr.utils.download_util import load_file_from_url
dd_models_path = os.path.join(models_path, "mmdet")
def list_models(model_path):
model_list = modelloader.load_models(model_path=model_path, ext_filter=[".pth"])
def modeltitle(path, shorthash):
abspath = os.path.abspath(path)
if abspath.startswith(model_path):
name = abspath.replace(model_path, '')
else:
name = os.path.basename(path)
if name.startswith("\\") or name.startswith("/"):
name = name[1:]
shortname = os.path.splitext(name.replace("/", "_").replace("\\", "_"))[0]
return f'{name} [{shorthash}]', shortname
models = []
for filename in model_list:
h = model_hash(filename)
title, short_model_name = modeltitle(filename, h)
models.append(title)
return models
print('Installing requirements for eyemask')
if not is_installed("dlib"):
python = sys.executable
run(f'"{python}" -m pip install setuptools', desc="Installing setuptools", errdesc="Couldn't install setuptools")
run(f'"{python}" -m pip install dlib', desc="Installing dlib", errdesc="Couldn't install dlib")
if not is_installed("mmdet"):
python = sys.executable
run(f'"{python}" -m pip install -U openmim', desc="Installing openmim", errdesc="Couldn't install openmim")
run(f'"{python}" -m mim install mmcv-full', desc=f"Installing mmcv-full", errdesc=f"Couldn't install mmcv-full")
run(f'"{python}" -m pip install mmdet', desc=f"Installing mmdet", errdesc=f"Couldn't install mmdet")
if (len(list_models(dd_models_path)) == 0):
print("No detection models found, downloading...")
bbox_path = os.path.join(dd_models_path, "bbox")
segm_path = os.path.join(dd_models_path, "segm")
load_file_from_url("https://huggingface.co/dustysys/ddetailer/resolve/main/mmdet/bbox/mmdet_anime-face_yolov3.pth", bbox_path)
load_file_from_url("https://huggingface.co/dustysys/ddetailer/raw/main/mmdet/bbox/mmdet_anime-face_yolov3.py", bbox_path)
load_file_from_url("https://huggingface.co/dustysys/ddetailer/resolve/main/mmdet/segm/mmdet_dd-person_mask2former.pth", segm_path)
load_file_from_url("https://huggingface.co/dustysys/ddetailer/raw/main/mmdet/segm/mmdet_dd-person_mask2former.py", segm_path)
git_clone("https://github.com/isl-org/MiDaS.git", "repositories/midas", "midas")

210
javascript/dialog.js Normal file
View File

@ -0,0 +1,210 @@
var dialog = (function () {
var DIALOG_FADE_IN_SPEED = 250;
var DIALOG_FADE_OUT_SPEED = 150;
var $mainContainer = null;
var onClose = null;
function getContainer() {
if (! $mainContainer) {
$mainContainer = $('#dialogs-container');
}
return $mainContainer;
}
var mainLayout = (
'<div id="{0}-modal" class="modal" tabindex="-1">' +
'<div class="modal-header">' +
'<h5 class="modal-title"></h5>' +
'</div>' +
'<div id="{0}-modal-content" class="modal-content"></div>' +
'<div class="modal-footer"></div>' +
'</div>'
);
function getContent(opt) {
return mainLayout.format(opt.id);
}
function getButton(btnOpt) {
var template = '<button {0} type="button" class="button {1}">{2}</button>';
var id = btnOpt.id ? ' id="' + btnOpt.id + '-modal-btn" ' : '';
var className = btnOpt.className ? btnOpt.className : '';
var text = btnOpt.text;
return template.format(id, className, text);
}
function close() {
getContainer()
.removeClass('open')
.find('div.modal').fadeOut(DIALOG_FADE_OUT_SPEED);
if (onClose && onClose.call) {
onClose();
}
}
function onFooterBtnClick(data) {
if (data.action && data.action.call) {
data.action.call(this, data.params || []);
}
if (! data.dontClose) {
close();
}
}
function show(opt) {
onClose = null;
close();
if ((typeof opt).toLowerCase() === 'string') {
opt = {
content: opt
};
}
if (! opt.id) {
opt.id = +new Date;
}
onClose = (opt.onClose && opt.onClose.call) ? opt.onClose : null;
var dialogId = '#{0}-modal'.format(opt.id);
if (! getContainer().find(dialogId).length) {
getContainer().append(getContent(opt));
}
$dialog = $(dialogId);
if (opt.big || opt.imageSrc) {
var windowHeight = $(window).height();
var minHeight = parseInt(windowHeight * 0.8) - 100;
var top = parseInt(windowHeight * 0.08);
$dialog.css('top', top + 'px');
if (! opt.dontStretch) {
$dialog.find('.modal-content').css('min-height', minHeight);
}
$dialog.addClass('modal-big');
if (opt.imageSrc) {
$dialog.find('.modal-content').css({
'background-image': 'url(' + opt.imageSrc + ')',
'background-size': 'contain',
'background-repeat' : 'no-repeat',
'background-position': 'center center'
});
}
}
if (opt.maxWidth) {
$dialog.css('max-width', opt.maxWidth);
}
if (opt.width) {
$dialog.css('width', opt.width);
}
if (opt.top) {
$dialog.css('top', opt.top);
}
$dialog
.find('.modal-title').html(opt.title).end()
.find('.modal-content').html(opt.content || opt.text || '').end()
.bindClick(function (e) {
e.stopPropagation();
})
.fadeIn(DIALOG_FADE_IN_SPEED);
getContainer().addClass('open');
if (! opt.title) {
$dialog
.find('.modal-header').hide().end()
.find('.modal-content').css('padding', '30px 20px')
}
if (opt.removeWrapper) {
$dialog.find('.modal-content *').first().unwrap();
}
$footer = $dialog.find('.modal-footer').first().empty();
if (! opt.disableOverlay) {
getContainer().bindClick(close);
} else {
getContainer().off('click');
}
if (! opt.buttons || ! opt.buttons.length) {
opt.buttons = [{
id: 'modal-close-button',
text: 'Ok'
}];
}
for (var i = 0, len = opt.buttons.length; i < len; i++) {
if (! opt.buttons[i].id) {
opt.buttons[i].id = Math.floor(Math.random() * 1000000);
}
$footer
.append(getButton(opt.buttons[i]))
.find('#' + opt.buttons[i].id + '-modal-btn')
.bindClick(onFooterBtnClick, [opt.buttons[i]]);
}
return $dialog;
}
function showConfirm(text, action) {
this.show({
id: 'main-confirmation',
title: 'Confirmation',
content: text,
buttons: [
{
text: 'Yes',
className: 'danger',
action: action
}, {
text: 'No'
}
]
});
}
function showImage(src, title, buttons, maxWidth) {
this.show({
id: 'image-dialog',
imageSrc: '/sdapi/v1/eyemask/v1/static/images/' + src,
big: true,
maxWidth: maxWidth || null,
title: title || null,
buttons: buttons || null
});
}
function showError(message) {
this.show({
id: 'error-dialog',
title: 'Error',
content: message,
});
}
function hide() {
close();
}
return {
show: show,
hide: hide,
confirm: showConfirm,
image: showImage,
error: showError,
};
})();

196
javascript/eyemask.js Normal file
View File

@ -0,0 +1,196 @@
document.addEventListener('DOMContentLoaded', function() {
window.BODY_SELECTOR = '.mx-auto.container'
toastr.options = {
target: window.BODY_SELECTOR,
timeOut: 3500
};
window.gradioRoot = gradioApp().querySelector('.gradio-container');
window.$gradioRoot = $(window.gradioRoot);
onUiLoaded(EyeMaskController.load);
$gradioRoot.append('<div id="dialogs-container"></div>')
});
const EyeMaskController = (function () {
let container = null;
let config = {};
const LS_PREFIX = 'em-save-';
const TABS = ['txt2img', 'img2img'];
const emTitles = {
'\u21A9\uFE0F': 'Load current image eyemask params',
'Redraw original': 'Change seed after each batch',
'Include mask': 'Include mask image in result'
};
function getContainer() {
if (!container) {
container = gradioApp().getElementById('eye-mask-container');
}
return container
}
function getApiUrl(path) {
return '/sdapi/v1/eyemask/v1' + path;
}
function loadTitles() {
gradioApp().querySelectorAll('span, button, select, p').forEach(function(elem) {
if (elem) {
let tooltip = emTitles[elem.textContent] || emTitles[elem.value];
if (tooltip) {
elem.title = tooltip;
}
}
});
}
function getConfig() {
$.ajax({
url: getApiUrl('/config.json'),
dataType: 'json',
async: false,
cache: false,
success: function(data) {
config = data;
}
});
}
function getAllIds(id) {
let result = [];
result.push('em-{0}-txt2img'.format(id));
result.push('em-{0}-img2img'.format(id));
result.push('em-emb-{0}-txt2img'.format(id));
result.push('em-emb-{0}-img2img'.format(id));
return result;
}
function loadPlaceHolders() {
if (config.em_save_prompts) {
['txt2img_prompt', 'img2img_prompt'].forEach(handleSavedInput);
}
if (config.em_save_neg_prompts) {
['txt2img_neg_prompt', 'img2img_neg_prompt'].forEach(handleSavedInput);
}
if (config.em_save_em_prompts) {
getAllIds('prompt').forEach(handleSavedInput);
}
if (config.em_save_em_neg_prompts) {
getAllIds('negative-prompt').forEach(handleSavedInput);
}
if (config.em_save_settings) {
[
'enabled',
'count',
'mask-type',
'mask-padding',
'mask-steps',
'mask-blur',
'denoising-strength',
'full-res-padding',
'cfg',
'width',
'height',
'include-mask',
'padding-in-px',
'redraw-original',
'use-other-model',
'mask-model'
].forEach(function (id) {
getAllIds(id).forEach(handleSavedInput);
});
}
if (config.em_save_last_script) {
TABS.forEach(loadLastScript)
}
}
function handleSavedInput(id) {
let $el = $('#{0} textarea, #{0} select, #{0} input'.format(id));
let event = 'change input';
if (! $el.length) {
return;
}
let value = localStorage.getItem(LS_PREFIX + id);
if (value) {
switch ($el[0].type) {
case 'checkbox':
$el.prop('checked', value === 'true').triggerEvent(event);
break;
case 'radio':
$el.filter(':checked').prop('checked', false);
$el.filter('[value="{0}"]'.format(value)).prop('checked', true).triggerEvent(event);
break;
default:
$el.val(value).triggerEvent(event);
}
}
$el.on(event,function () {
let value = this.value;
if (this.type && this.type === 'checkbox') {
value = this.checked;
}
localStorage.setItem(LS_PREFIX + id, value);
});
if (id.indexOf('emb-enabled') > -1 && value === 'true') {
$('#' + id.replace('em-emb-enabled-', '') + '_script_container .cursor-pointer').triggerEvent('click');
}
if (id.indexOf('emb-use-other-model') > -1) {
setTimeout(function () {
$el.triggerEvent(event);
}, 0);
}
}
function loadLastScript(tab) {
let $select = $('#{0}_script_container #script_list select'.format(tab));
let value = localStorage.getItem(LS_PREFIX + 'last-script-' + tab);
$select.on('change', function () {
localStorage.setItem(LS_PREFIX + 'last-script-' + tab, this.value);
});
if (value) {
setTimeout(function () {
$select.val(value).triggerEvent('change');
},0);
}
}
function onFirstLoad() {
getConfig();
loadTitles();
loadPlaceHolders();
}
function load() {
container = getContainer();
onFirstLoad();
}
function showInfo() {
dialog.image('mask-types.jpg', 'Mask Types', null, '80%');
}
return {
load,
showInfo
};
}());

35
javascript/helpers.js Normal file
View File

@ -0,0 +1,35 @@
function log(m) {
console.log(m);
}
String.prototype.format = function() {
var args = arguments;
return this.replace(/{(\d+)}/g, function(match, number) {
return typeof args[number] != 'undefined' ? args[number] : match;
});
};
document.addEventListener('DOMContentLoaded', function() {
$.fn.bindClick = function (func, args) {
if (args) {
return this.off('click').on('click', function () {
func.apply(this, args);
});
} else {
return this.off('click').on('click', func);
}
};
$.fn.triggerEvent = function (event) {
if (! this.length) {
return this;
}
let el = this[0];
event.split(' ').forEach(function (evt) {
el.dispatchEvent(new Event(evt.trim()));
});
return this;
};
});

10996
javascript/jquery-3.6.3.min.js vendored Normal file

File diff suppressed because it is too large Load Diff

476
javascript/toastr.min.js vendored Normal file
View File

@ -0,0 +1,476 @@
/*
* Toastr
* Copyright 2012-2015
* Authors: John Papa, Hans Fjällemark, and Tim Ferrell.
* All Rights Reserved.
* Use, reproduction, distribution, and modification of this code is subject to the terms and
* conditions of the MIT license, available at http://www.opensource.org/licenses/mit-license.php
*
* ARIA Support: Greta Krafsig
*
* Project: https://github.com/CodeSeven/toastr
*/
/* global define */
(function (define) {
define(['jquery'], function ($) {
return (function () {
var $container;
var listener;
var toastId = 0;
var toastType = {
error: 'error',
info: 'info',
success: 'success',
warning: 'warning'
};
var toastr = {
clear: clear,
remove: remove,
error: error,
getContainer: getContainer,
info: info,
options: {},
subscribe: subscribe,
success: success,
version: '2.1.4',
warning: warning
};
var previousToast;
return toastr;
////////////////
function error(message, title, optionsOverride) {
return notify({
type: toastType.error,
iconClass: getOptions().iconClasses.error,
message: message,
optionsOverride: optionsOverride,
title: title
});
}
function getContainer(options, create) {
if (!options) { options = getOptions(); }
$container = $('#' + options.containerId);
if ($container.length) {
return $container;
}
if (create) {
$container = createContainer(options);
}
return $container;
}
function info(message, title, optionsOverride) {
return notify({
type: toastType.info,
iconClass: getOptions().iconClasses.info,
message: message,
optionsOverride: optionsOverride,
title: title
});
}
function subscribe(callback) {
listener = callback;
}
function success(message, title, optionsOverride) {
return notify({
type: toastType.success,
iconClass: getOptions().iconClasses.success,
message: message,
optionsOverride: optionsOverride,
title: title
});
}
function warning(message, title, optionsOverride) {
return notify({
type: toastType.warning,
iconClass: getOptions().iconClasses.warning,
message: message,
optionsOverride: optionsOverride,
title: title
});
}
function clear($toastElement, clearOptions) {
var options = getOptions();
if (!$container) { getContainer(options); }
if (!clearToast($toastElement, options, clearOptions)) {
clearContainer(options);
}
}
function remove($toastElement) {
var options = getOptions();
if (!$container) { getContainer(options); }
if ($toastElement && $(':focus', $toastElement).length === 0) {
removeToast($toastElement);
return;
}
if ($container.children().length) {
$container.remove();
}
}
// internal functions
function clearContainer (options) {
var toastsToClear = $container.children();
for (var i = toastsToClear.length - 1; i >= 0; i--) {
clearToast($(toastsToClear[i]), options);
}
}
function clearToast ($toastElement, options, clearOptions) {
var force = clearOptions && clearOptions.force ? clearOptions.force : false;
if ($toastElement && (force || $(':focus', $toastElement).length === 0)) {
$toastElement[options.hideMethod]({
duration: options.hideDuration,
easing: options.hideEasing,
complete: function () { removeToast($toastElement); }
});
return true;
}
return false;
}
function createContainer(options) {
$container = $('<div/>')
.attr('id', options.containerId)
.addClass(options.positionClass);
$container.appendTo($(options.target));
return $container;
}
function getDefaults() {
return {
tapToDismiss: true,
toastClass: 'toast',
containerId: 'toast-container',
debug: false,
showMethod: 'fadeIn', //fadeIn, slideDown, and show are built into jQuery
showDuration: 300,
showEasing: 'swing', //swing and linear are built into jQuery
onShown: undefined,
hideMethod: 'fadeOut',
hideDuration: 1000,
hideEasing: 'swing',
onHidden: undefined,
closeMethod: false,
closeDuration: false,
closeEasing: false,
closeOnHover: true,
extendedTimeOut: 1000,
iconClasses: {
error: 'toast-error',
info: 'toast-info',
success: 'toast-success',
warning: 'toast-warning'
},
iconClass: 'toast-info',
positionClass: 'toast-top-right',
timeOut: 5000, // Set timeOut and extendedTimeOut to 0 to make it sticky
titleClass: 'toast-title',
messageClass: 'toast-message',
escapeHtml: false,
target: 'body',
closeHtml: '<button type="button">&times;</button>',
closeClass: 'toast-close-button',
newestOnTop: true,
preventDuplicates: false,
progressBar: false,
progressClass: 'toast-progress',
rtl: false
};
}
function publish(args) {
if (!listener) { return; }
listener(args);
}
function notify(map) {
var options = getOptions();
var iconClass = map.iconClass || options.iconClass;
if (typeof (map.optionsOverride) !== 'undefined') {
options = $.extend(options, map.optionsOverride);
iconClass = map.optionsOverride.iconClass || iconClass;
}
if (shouldExit(options, map)) { return; }
toastId++;
$container = getContainer(options, true);
var intervalId = null;
var $toastElement = $('<div/>');
var $titleElement = $('<div/>');
var $messageElement = $('<div/>');
var $progressElement = $('<div/>');
var $closeElement = $(options.closeHtml);
var progressBar = {
intervalId: null,
hideEta: null,
maxHideTime: null
};
var response = {
toastId: toastId,
state: 'visible',
startTime: new Date(),
options: options,
map: map
};
personalizeToast();
displayToast();
handleEvents();
publish(response);
if (options.debug && console) {
console.log(response);
}
return $toastElement;
function escapeHtml(source) {
if (source == null) {
source = '';
}
return source
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
function personalizeToast() {
setIcon();
setTitle();
setMessage();
setCloseButton();
setProgressBar();
setRTL();
setSequence();
setAria();
}
function setAria() {
var ariaValue = '';
switch (map.iconClass) {
case 'toast-success':
case 'toast-info':
ariaValue = 'polite';
break;
default:
ariaValue = 'assertive';
}
$toastElement.attr('aria-live', ariaValue);
}
function handleEvents() {
if (options.closeOnHover) {
$toastElement.hover(stickAround, delayedHideToast);
}
if (!options.onclick && options.tapToDismiss) {
$toastElement.click(hideToast);
}
if (options.closeButton && $closeElement) {
$closeElement.click(function (event) {
if (event.stopPropagation) {
event.stopPropagation();
} else if (event.cancelBubble !== undefined && event.cancelBubble !== true) {
event.cancelBubble = true;
}
if (options.onCloseClick) {
options.onCloseClick(event);
}
hideToast(true);
});
}
if (options.onclick) {
$toastElement.click(function (event) {
options.onclick(event);
hideToast();
});
}
}
function displayToast() {
$toastElement.hide();
$toastElement[options.showMethod](
{duration: options.showDuration, easing: options.showEasing, complete: options.onShown}
);
if (options.timeOut > 0) {
intervalId = setTimeout(hideToast, options.timeOut);
progressBar.maxHideTime = parseFloat(options.timeOut);
progressBar.hideEta = new Date().getTime() + progressBar.maxHideTime;
if (options.progressBar) {
progressBar.intervalId = setInterval(updateProgress, 10);
}
}
}
function setIcon() {
if (map.iconClass) {
$toastElement.addClass(options.toastClass).addClass(iconClass);
}
}
function setSequence() {
if (options.newestOnTop) {
$container.prepend($toastElement);
} else {
$container.append($toastElement);
}
}
function setTitle() {
if (map.title) {
var suffix = map.title;
if (options.escapeHtml) {
suffix = escapeHtml(map.title);
}
$titleElement.append(suffix).addClass(options.titleClass);
$toastElement.append($titleElement);
}
}
function setMessage() {
if (map.message) {
var suffix = map.message;
if (options.escapeHtml) {
suffix = escapeHtml(map.message);
}
$messageElement.append(suffix).addClass(options.messageClass);
$toastElement.append($messageElement);
}
}
function setCloseButton() {
if (options.closeButton) {
$closeElement.addClass(options.closeClass).attr('role', 'button');
$toastElement.prepend($closeElement);
}
}
function setProgressBar() {
if (options.progressBar) {
$progressElement.addClass(options.progressClass);
$toastElement.prepend($progressElement);
}
}
function setRTL() {
if (options.rtl) {
$toastElement.addClass('rtl');
}
}
function shouldExit(options, map) {
if (options.preventDuplicates) {
if (map.message === previousToast) {
return true;
} else {
previousToast = map.message;
}
}
return false;
}
function hideToast(override) {
var method = override && options.closeMethod !== false ? options.closeMethod : options.hideMethod;
var duration = override && options.closeDuration !== false ?
options.closeDuration : options.hideDuration;
var easing = override && options.closeEasing !== false ? options.closeEasing : options.hideEasing;
// if ($(':focus', $toastElement).length && !override) {
// return;
// }
clearTimeout(progressBar.intervalId);
return $toastElement[method]({
duration: duration,
easing: easing,
complete: function () {
removeToast($toastElement);
clearTimeout(intervalId);
if (options.onHidden && response.state !== 'hidden') {
options.onHidden();
}
response.state = 'hidden';
response.endTime = new Date();
publish(response);
}
});
}
function delayedHideToast() {
if (options.timeOut > 0 || options.extendedTimeOut > 0) {
intervalId = setTimeout(hideToast, options.extendedTimeOut);
progressBar.maxHideTime = parseFloat(options.extendedTimeOut);
progressBar.hideEta = new Date().getTime() + progressBar.maxHideTime;
}
}
function stickAround() {
clearTimeout(intervalId);
progressBar.hideEta = 0;
$toastElement.stop(true, true)[options.showMethod](
{duration: options.showDuration, easing: options.showEasing}
);
}
function updateProgress() {
var percentage = ((progressBar.hideEta - (new Date().getTime())) / progressBar.maxHideTime) * 100;
$progressElement.width(percentage + '%');
}
}
function getOptions() {
return $.extend({}, getDefaults(), toastr.options);
}
function removeToast($toastElement) {
if (!$container) { $container = getContainer(); }
if ($toastElement.is(':visible')) {
return;
}
$toastElement.remove();
$toastElement = null;
if ($container.children().length === 0) {
$container.remove();
previousToast = undefined;
}
}
})();
});
}(typeof define === 'function' && define.amd ? define : function (deps, factory) {
if (typeof module !== 'undefined' && module.exports) { //Node
module.exports = factory(require('jquery'));
} else {
window.toastr = factory(window.jQuery);
}
}));

12213
models/haarcascade_eye.xml Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

22
scripts/em_api.py Normal file
View File

@ -0,0 +1,22 @@
'''
API for Eye Mask - Stable Diffusion Web UI extension for mark and redraw eyes/faces.
Core logic is in eyemask.api.
Author: ilian.iliev
Since: 09.01.2023
'''
import os
import sys
from modules import scripts
sys.path.append(os.path.join(scripts.basedir(), 'scripts'))
import modules.script_callbacks as script_callbacks
from eyemask.api import EyeMaskApi
try:
api = EyeMaskApi()
script_callbacks.on_app_started(api.start)
except:
pass

35
scripts/em_script.py Normal file
View File

@ -0,0 +1,35 @@
'''
Eye Mask - Stable Diffusion Web UI extension for mark and redraw eyes/faces.
Core logic for 'run' method is in eyemask.script.
Author: ilian.iliev
Since: 09.01.2023
'''
import os
import sys
from modules import scripts
sys.path.append(os.path.join(scripts.basedir(), 'scripts'))
from eyemask import constants, ui, script as eye_mask_script
class EyeMaskScript(scripts.Script):
def __init__(self, *k, **kw):
self.eye_mask_core = eye_mask_script.EyeMasksCore()
self.eye_mask_ui = ui.EyeMaskUI(self)
super().__init__()
def title(self):
return constants.script_name
def show(self, is_img2img):
return True
def ui(self, is_img2img):
return self.eye_mask_ui.render(is_img2img)
def run(self, *args, **kwargs):
return self.eye_mask_core.execute(*args, **kwargs)

View File

@ -0,0 +1,42 @@
'''
Eye Mask - Stable Diffusion Web UI extension (embedded version) for mark and redraw eyes/faces.
Core logic is in eyemask.script_embedded.
Author: ilian.iliev
Since: 09.01.2023
'''
import os
import sys
from modules import scripts
sys.path.append(os.path.join(scripts.basedir(), 'scripts'))
from eyemask import constants, ui, script_embedded as eye_mask_script
import modules.shared as shared
class EyeMaskEmbeddedScript(scripts.Script):
def __init__(self, *k, **kw):
self.eye_mask_core = eye_mask_script.EyeMasksEmbeddedCore()
self.eye_mask_ui = ui.EyeMaskUI(self)
super().__init__()
def title(self):
return constants.script_name
def show(self, is_img2img):
try:
return scripts.AlwaysVisible if shared.opts.em_show_embedded_version else False
except Exception as e:
return False
def ui(self, is_img2img):
return self.eye_mask_ui.render(is_img2img)
def process(self, p, *args):
return self.eye_mask_core.execute_process(p, *args)
def postprocess(self, p, processed, *args):
return self.eye_mask_core.execute_postprocess(p, processed, *args)

22
scripts/em_settings.py Normal file
View File

@ -0,0 +1,22 @@
import modules.shared as shared
from modules import scripts
def on_ui_settings():
section = ('eyemask', 'Eye Mask')
options = [
('em_show_embedded_version', False, 'Show embedded version'),
('em_save_masks', False, 'Save masks'),
('em_outdir_masks', 'extensions/eyemask/outputs/masks', 'Output directory for masks'),
('em_wildcards_in_original', True, 'Replace wildcards in original prompt'),
('em_save_prompts', False, 'Save last prompt'),
('em_save_neg_prompts', False, 'Save last negative prompt'),
('em_save_em_prompts', False, 'Save last mask prompt'),
('em_save_em_neg_prompts', False, 'Save last mask negative prompt'),
('em_save_last_script', False, 'Save last script'),
('em_save_settings', False, 'Save all settings'),
('em_dev_mode', False, 'Dev mode'),
]
for opt in options:
shared.opts.add_option(opt[0], shared.OptionInfo(opt[1], opt[2], section=section))
scripts.script_callbacks.on_ui_settings(on_ui_settings)

View File

@ -0,0 +1,4 @@
from os.path import dirname, basename, isfile, join
import glob
modules = glob.glob(join(dirname(__file__), "*.py"))
__all__ = [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')]

View File

@ -0,0 +1,3 @@
import models
import utils
from . api import EyeMaskApi

View File

@ -0,0 +1,55 @@
import os
import gradio as gr
from fastapi import FastAPI, Body, HTTPException, Request, Response
from fastapi.responses import FileResponse
from .. constants import script_static_dir
from .. import script as eye_mask_script
from . utils import encode_pil_to_base64, decode_base64_to_image
from . models import *
class EyeMaskApi():
def __init__(self):
self.core = eye_mask_script.EyeMasksCore()
BASE_PATH = '/sdapi/v1/eyemask'
VERSION = 1
def get_path(self, path):
return f"{self.BASE_PATH}/v{self.VERSION}{path}"
def add_api_route(self, path: str, endpoint, **kwargs):
# authenticated requests can be implemented here
return self.app.add_api_route(self.get_path(path), endpoint, **kwargs)
def start(self, _: gr.Blocks, app: FastAPI):
self.app = app
self.add_api_route('/mask_list', self.mask_list, methods=['GET'])
self.add_api_route('/generate_mask', self.generate_mask, methods=['POST'], response_model=SingleImageResponse)
self.add_api_route('/static/{path:path}', self.static, methods=['GET'])
self.add_api_route('/config.json', self.get_config, methods=['GET'])
def mask_list(self):
''' Get masks list '''
return { 'mask_list': list(eye_mask_script.EyeMasksCore.MASK_TYPES) }
def generate_mask(self, req: GenerateMaskRequest):
''' Generate mask by type '''
image = decode_base64_to_image(req.image)
mask, mask_success = self.core.generate_mask(image, int(req.mask_type))
return SingleImageResponse(image=encode_pil_to_base64(mask))
def static(self, path: str):
''' Serve static files '''
static_file = os.path.join(script_static_dir, path)
if static_file is not None:
return FileResponse(static_file)
raise HTTPException(status_code=404, detail='Static file not found')
def get_config(self):
return FileResponse('config.json')

View File

@ -0,0 +1,12 @@
from pydantic import BaseModel, Field
class SingleImageRequest(BaseModel):
image: str = Field(default="", title="Image", description="Image to work on, must be a Base64 string containing the image's data.")
class SingleImageResponse(BaseModel):
image: str = Field(default="", title="Image", description="Generated image, a Base64 string containing the image's data.")
class GenerateMaskRequest(SingleImageRequest):
mask_type: int = Field(default="", title="Mask Type", description="Mask type to work with")

View File

@ -0,0 +1,47 @@
import base64
import io
from io import BytesIO
from fastapi import APIRouter, Depends, FastAPI, HTTPException, Request, Response
from modules.api.models import *
from PIL import PngImagePlugin,Image
import piexif
import piexif.helper
def decode_base64_to_image(encoding):
if encoding.startswith("data:image/"):
encoding = encoding.split(";")[1].split(",")[1]
try:
image = Image.open(BytesIO(base64.b64decode(encoding)))
return image
except Exception as err:
raise HTTPException(status_code=500, detail="Invalid encoded image")
def encode_pil_to_base64(image):
with io.BytesIO() as output_bytes:
if opts.samples_format.lower() == 'png':
use_metadata = False
metadata = PngImagePlugin.PngInfo()
for key, value in image.info.items():
if isinstance(key, str) and isinstance(value, str):
metadata.add_text(key, value)
use_metadata = True
image.save(output_bytes, format="PNG", pnginfo=(metadata if use_metadata else None), quality=opts.jpeg_quality)
elif opts.samples_format.lower() in ("jpg", "jpeg", "webp"):
parameters = image.info.get('parameters', None)
exif_bytes = piexif.dump({
"Exif": { piexif.ExifIFD.UserComment: piexif.helper.UserComment.dump(parameters or "", encoding="unicode") }
})
if opts.samples_format.lower() in ("jpg", "jpeg"):
image.save(output_bytes, format="JPEG", exif = exif_bytes, quality=opts.jpeg_quality)
else:
image.save(output_bytes, format="WEBP", exif = exif_bytes, quality=opts.jpeg_quality)
else:
raise HTTPException(status_code=500, detail="Invalid image format")
bytes_data = output_bytes.getvalue()
return base64.b64encode(bytes_data)

View File

@ -0,0 +1,9 @@
import os
from modules import scripts
script_name = 'Eye Mask'
script_base_dir = scripts.basedir()
script_static_dir = os.path.join(script_base_dir, 'static')
script_models_dir = os.path.join(script_base_dir, 'models')
script_wildcards_dir = os.path.join(script_base_dir, 'wildcards')

View File

@ -0,0 +1,216 @@
import os
import dlib
import cv2
import copy
import numpy as np
from PIL import Image, ImageDraw
from .constants import script_models_dir
from .utils import expand_polygon, calculate_distance
from .modules import depthmap, mmdetdd
depthmap_generator = depthmap.SimpleDepthMapGenerator()
try:
landmark_detector = dlib.shape_predictor(
os.path.join(script_models_dir, 'shape_predictor_68_face_landmarks.dat')
)
except Exception as e:
# when reloading the module, landmark_detector is already loaded
# and the file cant be opened again
pass
def _get_image_mask(image):
width, height = image.size
return np.full((height, width, 3), 0, dtype=np.uint8)
def _get_detected_faces_dlib(image):
# Load the pre-trained model for detecting facial landmarks
face_detector = dlib.get_frontal_face_detector()
# Convert the image to grayscale
frame = np.array(image)
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# Detect faces in the image
return face_detector(gray, 1), gray
def _calculate_mask_padding(p1, p2, percents):
distance = calculate_distance((p1.x, p1.y), (p2.x, p2.y))
value = int(distance * (percents / 100))
return value
def get_eyes_mask_dlib(init_image, padding=20, in_pixels=False):
mask = _get_image_mask(init_image)
faces, gray = _get_detected_faces_dlib(init_image)
padding_original = padding
if not len(faces):
return Image.fromarray(mask), False
for face in faces:
# Use the landmark detector to find facial landmarks
landmarks = landmark_detector(gray, face)
# Extract the coordinates of the left and right eyes
left_eye_x = [landmarks.part(i).x for i in range(36, 42)]
left_eye_y = [landmarks.part(i).y for i in range(36, 42)]
right_eye_x = [landmarks.part(i).x for i in range(42, 48)]
right_eye_y = [landmarks.part(i).y for i in range(42, 48)]
# Calculate mask padding
if not in_pixels:
padding = _calculate_mask_padding(landmarks.part(36), landmarks.part(39), padding_original)
# Draw a filled white polygon around the left eye
left_eye_points = []
for i in range(len(left_eye_x)):
left_eye_points.append([left_eye_x[i], left_eye_y[i]])
left_eye_points = expand_polygon(left_eye_points, padding)
left_eye_points = np.array(left_eye_points, np.int32)
left_eye_points = left_eye_points.reshape((-1, 1, 2))
cv2.fillPoly(mask, [left_eye_points], (255, 255, 255))
# Calculate mask padding
if not in_pixels:
padding = _calculate_mask_padding(landmarks.part(42), landmarks.part(45), padding_original)
# Draw a filled white polygon around the right eye
right_eye_points = []
for i in range(len(right_eye_x)):
right_eye_points.append([right_eye_x[i], right_eye_y[i]])
right_eye_points = expand_polygon(right_eye_points, padding)
right_eye_points = np.array(right_eye_points, np.int32)
right_eye_points = right_eye_points.reshape((-1, 1, 2))
cv2.fillPoly(mask, [right_eye_points], (255, 255, 255))
return Image.fromarray(mask), True
def get_face_mask_dlib(init_image, padding=20, in_pixels=False):
mask = _get_image_mask(init_image)
faces, gray = _get_detected_faces_dlib(init_image)
padding_original = padding
if not len(faces):
return Image.fromarray(mask), False
for face in faces:
# Use the landmark detector to find facial landmarks
landmarks = landmark_detector(gray, face)
face_x = [landmarks.part(i).x for i in range(0, 17)] + [landmarks.part(i).x for i in reversed(range(17, 27))]
face_y = [landmarks.part(i).y for i in range(0, 17)] + [landmarks.part(i).y for i in reversed(range(17, 27))]
# Calculate mask padding
if not in_pixels:
padding = _calculate_mask_padding(landmarks.part(0), landmarks.part(16), padding_original)
# Draw a filled white polygon around the face
face_points = []
for i in range(len(face_x)):
face_points.append([face_x[i], face_y[i]])
face_points = expand_polygon(face_points, padding)
face_points = np.array(face_points, np.int32)
face_points = face_points.reshape((-1, 1, 2))
cv2.fillPoly(mask, [face_points], (255, 255, 255))
return Image.fromarray(mask), True
def get_face_mask_depth(init_image):
mask = _get_image_mask(init_image)
faces, gray = _get_detected_faces_dlib(init_image)
if len(faces) != 1:
return Image.fromarray(mask), False
body_mask, body_mask_success = get_body_mask_depth(init_image)
if not body_mask_success:
return Image.fromarray(mask), False
face = faces[0]
landmarks = landmark_detector(gray, face)
lowest_point = None
for i in range(0, 17):
point = landmarks.part(i)
if lowest_point is None or point.y > lowest_point.y:
lowest_point = point
width, height = body_mask.size
for x in range(0, width):
for y in range(lowest_point.y, height):
body_mask.putpixel((x, y), (0, 0, 0))
return body_mask, True
def get_body_mask_depth(init_image):
def remap_range(value, minIn, MaxIn, minOut, maxOut):
if value > MaxIn: value = MaxIn;
if value < minIn: value = minIn;
finalValue = ((value - minIn) / (MaxIn - minIn)) * (maxOut - minOut) + minOut
return finalValue
def create_depth_mask_from_depth_map(img, treshold, clean_cut):
img = copy.deepcopy(img.convert("RGBA"))
mask_img = copy.deepcopy(img.convert("L"))
mask_datas = mask_img.getdata()
datas = img.getdata()
newData = []
maxD = max(mask_datas)
if clean_cut and treshold == 0:
treshold = 128
for i in range(len(mask_datas)):
if clean_cut and mask_datas[i] > treshold:
newrgb = 255
elif mask_datas[i] > treshold and not clean_cut:
newrgb = int(remap_range(mask_datas[i],treshold,255,0,255))
else:
newrgb = 0
newData.append((newrgb,newrgb,newrgb,255))
img.putdata(newData)
return img
try:
d_m = depthmap_generator.calculate_depth_maps(init_image, init_image.width, init_image.height, 1, False)
d_m = create_depth_mask_from_depth_map(d_m, 128, True)
return d_m, True
except Exception as e:
print(e)
return _get_image_mask(init_image), False
def get_face_mask_mmdet(init_image):
mask = _get_image_mask(init_image)
faces, gray = _get_detected_faces_dlib(init_image)
if len(faces) != 1:
return Image.fromarray(mask), False
body_mask, body_mask_success = get_body_mask_mmdet(init_image)
if not body_mask_success:
return Image.fromarray(mask), False
face = faces[0]
landmarks = landmark_detector(gray, face)
lowest_point = None
for i in range(0, 17):
point = landmarks.part(i)
if lowest_point is None or point.y > lowest_point.y:
lowest_point = point
width, height = body_mask.size
for x in range(0, width):
for y in range(lowest_point.y, height):
body_mask.putpixel((x, y), 0)
return body_mask, True
def get_body_mask_mmdet(init_image):
try:
mask = mmdetdd.get_person_mask(init_image)
return mask, True
except Exception as e:
print(e)
return _get_image_mask(init_image), False

View File

@ -0,0 +1,4 @@
from os.path import dirname, basename, isfile, join
import glob
modules = glob.glob(join(dirname(__file__), "*.py"))
__all__ = [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')]

View File

@ -0,0 +1,156 @@
import torch, gc
import cv2
import requests
import os.path
import contextlib
from PIL import Image
from modules.shared import opts, cmd_opts
from modules import processing, images, shared, devices
from torchvision.transforms import Compose
from repositories.midas.midas.dpt_depth import DPTDepthModel
from repositories.midas.midas.midas_net import MidasNet
from repositories.midas.midas.midas_net_custom import MidasNet_small
from repositories.midas.midas.transforms import Resize, NormalizeImage, PrepareForNet
import numpy as np
class SimpleDepthMapGenerator(object):
def calculate_depth_maps(self,image,img_x,img_y,model_type,invert_depth):
try:
def download_file(filename, url):
# print("Downloading midas model weights to %s" % filename)
with open(filename, 'wb') as fout:
response = requests.get(url, stream=True)
response.raise_for_status()
# Write response data to file
for block in response.iter_content(4096):
fout.write(block)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# model path and name
model_dir = "./models/midas"
# create path to model if not present
os.makedirs(model_dir, exist_ok=True)
# print("Loading midas model weights ..")
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
#"dpt_large"
if model_type == 0:
model_path = f"{model_dir}/dpt_large-midas-2f21e586.pt"
# print(model_path)
if not os.path.exists(model_path):
download_file(model_path,"https://github.com/intel-isl/DPT/releases/download/1_0/dpt_large-midas-2f21e586.pt")
model = DPTDepthModel(
path=model_path,
backbone="vitl16_384",
non_negative=True,
)
net_w, net_h = 384, 384
resize_mode = "minimal"
normalization = NormalizeImage(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
#"midas_v21"
elif model_type == 1:
model_path = f"{model_dir}/midas_v21-f6b98070.pt"
# print(model_path)
if not os.path.exists(model_path):
download_file(model_path,"https://github.com/AlexeyAB/MiDaS/releases/download/midas_dpt/midas_v21-f6b98070.pt")
model = MidasNet(model_path, non_negative=True)
net_w, net_h = 384, 384
resize_mode="upper_bound"
normalization = NormalizeImage(
mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]
)
#"midas_v21_small"
elif model_type == 2:
model_path = f"{model_dir}/midas_v21_small-70d6b9c8.pt"
# print(model_path)
if not os.path.exists(model_path):
download_file(model_path,"https://github.com/AlexeyAB/MiDaS/releases/download/midas_dpt/midas_v21_small-70d6b9c8.pt")
model = MidasNet_small(model_path, features=64, backbone="efficientnet_lite3", exportable=True, non_negative=True, blocks={'expand': True})
net_w, net_h = 256, 256
resize_mode="upper_bound"
normalization = NormalizeImage(
mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]
)
# init transform
transform = Compose(
[
Resize(
img_x,
img_y,
resize_target=None,
keep_aspect_ratio=True,
ensure_multiple_of=32,
resize_method=resize_mode,
image_interpolation_method=cv2.INTER_CUBIC,
),
normalization,
PrepareForNet(),
]
)
model.eval()
# optimize
if device == torch.device("cuda"):
model = model.to(memory_format=torch.channels_last)
if not cmd_opts.no_half:
model = model.half()
model.to(device)
img = cv2.cvtColor(np.asarray(image), cv2.COLOR_BGR2RGB) / 255.0
img_input = transform({"image": img})["image"]
precision_scope = torch.autocast if shared.cmd_opts.precision == "autocast" and device == torch.device("cuda") else contextlib.nullcontext
# compute
with torch.no_grad(), precision_scope("cuda"):
sample = torch.from_numpy(img_input).to(device).unsqueeze(0)
if device == torch.device("cuda"):
sample = sample.to(memory_format=torch.channels_last)
if not cmd_opts.no_half:
sample = sample.half()
prediction = model.forward(sample)
prediction = (
torch.nn.functional.interpolate(
prediction.unsqueeze(1),
size=img.shape[:2],
mode="bicubic",
align_corners=False,
)
.squeeze()
.cpu()
.numpy()
)
# output
depth = prediction
numbytes=2
depth_min = depth.min()
depth_max = depth.max()
max_val = (2**(8*numbytes))-1
# check output before normalizing and mapping to 16 bit
if depth_max - depth_min > np.finfo("float").eps:
out = max_val * (depth - depth_min) / (depth_max - depth_min)
else:
out = np.zeros(depth.shape)
# single channel, 16 bit image
img_output = out.astype("uint16")
# # invert depth map
if invert_depth:
img_output = cv2.bitwise_not(img_output)
# three channel, 8 bits per channel image
img_output2 = np.zeros_like(image)
img_output2[:,:,0] = img_output / 256.0
img_output2[:,:,1] = img_output / 256.0
img_output2[:,:,2] = img_output / 256.0
img = Image.fromarray(img_output2)
return img
except Exception:
raise
finally:
del model
gc.collect()
devices.torch_gc()

View File

@ -0,0 +1,239 @@
import os
import cv2
from PIL import Image
import numpy as np
from modules import shared, modelloader
from modules.sd_models import model_hash
from modules.paths import models_path
dd_models_path = os.path.join(models_path, "mmdet")
def get_person_mask(image):
results_a = inference_mmdet_segm(image, 'segm\mmdet_dd-person_mask2former.pth [1c8dbe8d]', 30/100.0, 'A')
masks_a = create_segmasks(results_a)
masks_a = dilate_masks(masks_a, 4, 1)
masks_a = offset_masks(masks_a, 0, 0)
return masks_a[0]
def list_models(model_path):
model_list = modelloader.load_models(model_path=model_path, ext_filter=[".pth"])
def modeltitle(path, shorthash):
abspath = os.path.abspath(path)
if abspath.startswith(model_path):
name = abspath.replace(model_path, '')
else:
name = os.path.basename(path)
if name.startswith("\\") or name.startswith("/"):
name = name[1:]
shortname = os.path.splitext(name.replace("/", "_").replace("\\", "_"))[0]
return f'{name} [{shorthash}]', shortname
models = []
for filename in model_list:
h = model_hash(filename)
title, short_model_name = modeltitle(filename, h)
models.append(title)
return models
def modeldataset(model_shortname):
path = modelpath(model_shortname)
if ("mmdet" in path and "segm" in path):
dataset = 'coco'
else:
dataset = 'bbox'
return dataset
def modelpath(model_shortname):
model_list = modelloader.load_models(model_path=dd_models_path, ext_filter=[".pth"])
model_h = model_shortname.split("[")[-1].split("]")[0]
for path in model_list:
if ( model_hash(path) == model_h):
return path
def update_result_masks(results, masks):
for i in range(len(masks)):
boolmask = np.array(masks[i], dtype=bool)
results[2][i] = boolmask
return results
def create_segmask_preview(results, image):
labels = results[0]
bboxes = results[1]
segms = results[2]
cv2_image = np.array(image)
cv2_image = cv2_image[:, :, ::-1].copy()
for i in range(len(segms)):
color = np.full_like(cv2_image, np.random.randint(100, 256, (1, 3), dtype=np.uint8))
alpha = 0.2
color_image = cv2.addWeighted(cv2_image, alpha, color, 1-alpha, 0)
cv2_mask = segms[i].astype(np.uint8) * 255
cv2_mask_bool = np.array(segms[i], dtype=bool)
centroid = np.mean(np.argwhere(cv2_mask_bool),axis=0)
centroid_x, centroid_y = int(centroid[1]), int(centroid[0])
cv2_mask_rgb = cv2.merge((cv2_mask, cv2_mask, cv2_mask))
cv2_image = np.where(cv2_mask_rgb == 255, color_image, cv2_image)
text_color = tuple([int(x) for x in ( color[0][0] - 100 )])
name = labels[i]
score = bboxes[i][4]
score = str(score)[:4]
text = name + ":" + score
cv2.putText(cv2_image, text, (centroid_x - 30, centroid_y), cv2.FONT_HERSHEY_DUPLEX, 0.4, text_color, 1, cv2.LINE_AA)
if ( len(segms) > 0):
preview_image = Image.fromarray(cv2.cvtColor(cv2_image, cv2.COLOR_BGR2RGB))
else:
preview_image = image
return preview_image
def is_allblack(mask):
cv2_mask = np.array(mask)
return cv2.countNonZero(cv2_mask) == 0
def bitwise_and_masks(mask1, mask2):
cv2_mask1 = np.array(mask1)
cv2_mask2 = np.array(mask2)
cv2_mask = cv2.bitwise_and(cv2_mask1, cv2_mask2)
mask = Image.fromarray(cv2_mask)
return mask
def subtract_masks(mask1, mask2):
cv2_mask1 = np.array(mask1)
cv2_mask2 = np.array(mask2)
cv2_mask = cv2.subtract(cv2_mask1, cv2_mask2)
mask = Image.fromarray(cv2_mask)
return mask
def dilate_masks(masks, dilation_factor, iter=1):
if dilation_factor == 0:
return masks
dilated_masks = []
kernel = np.ones((dilation_factor,dilation_factor), np.uint8)
for i in range(len(masks)):
cv2_mask = np.array(masks[i])
dilated_mask = cv2.dilate(cv2_mask, kernel, iter)
dilated_masks.append(Image.fromarray(dilated_mask))
return dilated_masks
def offset_masks(masks, offset_x, offset_y):
if (offset_x == 0 and offset_y == 0):
return masks
offset_masks = []
for i in range(len(masks)):
cv2_mask = np.array(masks[i])
offset_mask = cv2_mask.copy()
offset_mask = np.roll(offset_mask, -offset_y, axis=0)
offset_mask = np.roll(offset_mask, offset_x, axis=1)
offset_masks.append(Image.fromarray(offset_mask))
return offset_masks
def combine_masks(masks):
initial_cv2_mask = np.array(masks[0])
combined_cv2_mask = initial_cv2_mask
for i in range(1, len(masks)):
cv2_mask = np.array(masks[i])
combined_cv2_mask = cv2.bitwise_or(combined_cv2_mask, cv2_mask)
combined_mask = Image.fromarray(combined_cv2_mask)
return combined_mask
def create_segmasks(results):
segms = results[2]
segmasks = []
for i in range(len(segms)):
cv2_mask = segms[i].astype(np.uint8) * 255
mask = Image.fromarray(cv2_mask)
segmasks.append(mask)
return segmasks
import mmcv
from mmdet.core import get_classes
from mmdet.apis import (inference_detector,
init_detector)
def get_device():
device_id = shared.cmd_opts.device_id
if device_id is not None:
cuda_device = f"cuda:{device_id}"
else:
cuda_device = "cpu"
return cuda_device
def inference(image, modelname, conf_thres, label):
path = modelpath(modelname)
if ( "mmdet" in path and "bbox" in path ):
results = inference_mmdet_bbox(image, modelname, conf_thres, label)
elif ( "mmdet" in path and "segm" in path):
results = inference_mmdet_segm(image, modelname, conf_thres, label)
return results
def inference_mmdet_segm(image, modelname, conf_thres, label):
model_checkpoint = modelpath(modelname)
model_config = os.path.splitext(model_checkpoint)[0] + ".py"
model_device = get_device()
model = init_detector(model_config, model_checkpoint, device=model_device)
mmdet_results = inference_detector(model, np.array(image))
bbox_results, segm_results = mmdet_results
dataset = modeldataset(modelname)
classes = get_classes(dataset)
labels = [
np.full(bbox.shape[0], i, dtype=np.int32)
for i, bbox in enumerate(bbox_results)
]
n,m = bbox_results[0].shape
if (n == 0):
return [[],[],[]]
labels = np.concatenate(labels)
bboxes = np.vstack(bbox_results)
segms = mmcv.concat_list(segm_results)
filter_inds = np.where(bboxes[:,-1] > conf_thres)[0]
results = [[],[],[]]
for i in filter_inds:
results[0].append(label + "-" + classes[labels[i]])
results[1].append(bboxes[i])
results[2].append(segms[i])
return results
def inference_mmdet_bbox(image, modelname, conf_thres, label):
model_checkpoint = modelpath(modelname)
model_config = os.path.splitext(model_checkpoint)[0] + ".py"
model_device = get_device()
model = init_detector(model_config, model_checkpoint, device=model_device)
results = inference_detector(model, np.array(image))
cv2_image = np.array(image)
cv2_image = cv2_image[:, :, ::-1].copy()
cv2_gray = cv2.cvtColor(cv2_image, cv2.COLOR_BGR2GRAY)
segms = []
for (x0, y0, x1, y1, conf) in results[0]:
cv2_mask = np.zeros((cv2_gray.shape), np.uint8)
cv2.rectangle(cv2_mask, (int(x0), int(y0)), (int(x1), int(y1)), 255, -1)
cv2_mask_bool = cv2_mask.astype(bool)
segms.append(cv2_mask_bool)
n,m = results[0].shape
if (n == 0):
return [[],[],[]]
bboxes = np.vstack(results[0])
filter_inds = np.where(bboxes[:,-1] > conf_thres)[0]
results = [[],[],[]]
for i in filter_inds:
results[0].append(label)
results[1].append(bboxes[i])
results[2].append(segms[i])
return results

300
scripts/eyemask/script.py Normal file
View File

@ -0,0 +1,300 @@
import gc
import re
import modules.shared as shared
from modules import devices, images
from modules.processing import fix_seed, process_images, Processed, StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img
from . import mask_generator, utils, widlcards
from .state import SharedSettingsContext
class EyeMasksCore():
# Just comment mask type to disable it
MASK_TYPES = [
'Eyes dlib',
'Face dlib',
'Face depth',
'Body depth',
'Face mmdet',
'Body mmdet'
]
MASK_TYPE_EYES_DLIB = utils.index(MASK_TYPES, 'Eyes dlib')
MASK_TYPE_FACE_DLIB = utils.index(MASK_TYPES, 'Face dlib')
MASK_TYPE_FACE_DEPTH = utils.index(MASK_TYPES, 'Face depth')
MASK_TYPE_BODY_DEPTH = utils.index(MASK_TYPES, 'Body depth')
MASK_TYPE_FACE_MMDET = utils.index(MASK_TYPES, 'Face mmdet')
MASK_TYPE_BODY_MMDET = utils.index(MASK_TYPES, 'Body mmdet')
# Replaced in original image generation info with regex on each iteration
EM_DYNAMIC_PARAMS = [
'em_mask_prompt_final'
]
def execute(self, p,
em_redraw_original,
em_mask_type,
em_mask_prompt,
em_mask_negative_prompt,
em_mask_padding,
em_mask_padding_in_px,
em_mask_steps,
em_include_mask,
em_mask_blur,
em_denoising_strength,
em_cfg_scale,
em_width,
em_height,
em_inpaint_full_res,
em_inpaint_full_res_padding,
em_use_other_model,
em_model
):
em_params = {
'em_mask_prompt': em_mask_prompt,
'em_mask_negative_prompt': em_mask_negative_prompt,
'em_mask_type': em_mask_type,
'em_mask_padding': em_mask_padding,
'em_mask_steps': em_mask_steps,
'em_mask_blur': em_mask_blur,
'em_denoising_strength': em_denoising_strength,
'em_cfg_scale': em_cfg_scale,
'em_width': em_width,
'em_height': em_height,
'em_inpaint_full_res': em_inpaint_full_res,
'em_inpaint_full_res_padding': em_inpaint_full_res_padding
}
fix_seed(p)
seed = p.seed
iterations = p.n_iter
p.n_iter = 1
p.batch_size = 1
p.do_not_save_grid = True
p.do_not_save_samples = True
initial_info = None
orig_image_info = None
new_txt2img_info = None
new_img2img_info = None
is_txt2img = isinstance(p, StableDiffusionProcessingTxt2Img)
is_img2img = not is_txt2img
wildcards_generator_original = widlcards.WildcardsGenerator()
wildcards_generator_mask = widlcards.WildcardsGenerator()
if (is_img2img):
orig_image = p.init_images[0]
if orig_image.info is not None and 'parameters' in orig_image.info:
orig_image_info = orig_image.info['parameters']
init_orig_prompt = p.prompt or ''
else:
p_txt = p
p = StableDiffusionProcessingImg2Img(
init_images = None,
resize_mode = 0,
denoising_strength = em_denoising_strength,
mask = None,
mask_blur= em_mask_blur,
inpainting_fill = 1,
inpaint_full_res = em_inpaint_full_res,
inpaint_full_res_padding= em_inpaint_full_res_padding,
inpainting_mask_invert= 0,
sd_model=p_txt.sd_model,
outpath_samples=p_txt.outpath_samples,
outpath_grids=p_txt.outpath_grids,
prompt=p_txt.prompt,
negative_prompt=p_txt.negative_prompt,
styles=p_txt.styles,
seed=p_txt.seed,
subseed=p_txt.subseed,
subseed_strength=p_txt.subseed_strength,
seed_resize_from_h=p_txt.seed_resize_from_h,
seed_resize_from_w=p_txt.seed_resize_from_w,
sampler_name=p_txt.sampler_name,
n_iter=p_txt.n_iter,
steps=p_txt.steps,
cfg_scale=p_txt.cfg_scale,
width=p_txt.width,
height=p_txt.height,
tiling=p_txt.tiling,
)
p.do_not_save_grid = True
p.do_not_save_samples = True
init_orig_prompt = p_txt.prompt or ''
output_images = []
init_image = None
mask = None
mask_success = False
shared.state.job_count = 0
changing_model = em_use_other_model and em_model != 'None'
if changing_model:
em_params['em_mask_model'] = em_model
with SharedSettingsContext(changing_model) as context:
for n in range(iterations):
devices.torch_gc()
gc.collect()
start_seed = seed + n
new_image_generated = False
mask_prompt = em_mask_prompt
if em_mask_prompt is not None and len(em_mask_prompt.strip()) > 0:
mask_prompt = wildcards_generator_mask.build_prompt(em_mask_prompt)
em_params['em_mask_prompt_final'] = mask_prompt
if is_txt2img:
if init_image is None or em_redraw_original:
p_txt.seed = start_seed
init_image, new_txt2img_info, new_image_generated = self.create_new_image(
p_txt, em_params, init_orig_prompt, changing_model, context, wildcards_generator_original
)
else:
if init_image is None:
init_image, new_img2img_info, new_image_generated = self.create_new_image(
p, em_params, init_orig_prompt, changing_model, context, wildcards_generator_original
)
p.seed = start_seed
p.init_images = [init_image]
p.prompt = mask_prompt
p.negative_prompt = em_mask_negative_prompt
if new_image_generated:
mask, mask_success = self.get_mask(
em_mask_type, em_mask_padding, em_mask_padding_in_px, init_image,
p, start_seed, mask_prompt, initial_info
)
if mask_success:
p.image_mask = mask
p.steps = em_mask_steps
p.denoising_strength = em_denoising_strength
p.mask_blur = em_mask_blur
p.cfg_scale = em_cfg_scale
p.width = em_width
p.height = em_height
p.inpaint_full_res = em_inpaint_full_res
p.inpaint_full_res_padding= em_inpaint_full_res_padding
p.inpainting_mask_invert = 0
print(f"Processing {n + 1} / {iterations}.")
if changing_model:
context.apply_checkpoint(em_model)
shared.state.job_count += 1
processed = process_images(p)
save_prompt = p.prompt
if is_txt2img:
initial_info = new_txt2img_info
save_prompt = p_txt.prompt
elif not is_txt2img:
initial_info = new_img2img_info
try:
save_prompt = orig_image_info.split('\n')[0]
except Exception as e:
print(e)
save_prompt = orig_image_info
output_images.append(processed.images[0])
if em_include_mask and (n == iterations - 1 or (is_txt2img and em_redraw_original)):
output_images.append(mask)
shared.state.current_image = processed.images[0]
images.save_image(
processed.images[0],
p.outpath_samples,
"",
start_seed,
save_prompt,
shared.opts.samples_format,
info=self.update_info(initial_info, em_params),
p=p
)
devices.torch_gc()
gc.collect()
return Processed(p, output_images, seed, initial_info)
def generate_mask(self, init_image, em_mask_type, em_mask_padding=20, em_mask_padding_in_px=False):
if em_mask_type == self.MASK_TYPE_FACE_DLIB:
return mask_generator.get_face_mask_dlib(init_image, em_mask_padding, em_mask_padding_in_px)
elif em_mask_type == self.MASK_TYPE_FACE_DEPTH:
return mask_generator.get_face_mask_depth(init_image)
elif em_mask_type == self.MASK_TYPE_BODY_DEPTH:
return mask_generator.get_body_mask_depth(init_image)
elif em_mask_type == self.MASK_TYPE_FACE_MMDET:
return mask_generator.get_face_mask_mmdet(init_image)
elif em_mask_type == self.MASK_TYPE_BODY_MMDET:
return mask_generator.get_body_mask_mmdet(init_image)
else:
return mask_generator.get_eyes_mask_dlib(init_image, em_mask_padding, em_mask_padding_in_px)
def get_mask(self,
em_mask_type, em_mask_padding, em_mask_padding_in_px,
init_image, p, start_seed, mask_prompt, initial_info
):
mask, mask_success = self.generate_mask(init_image, em_mask_type, em_mask_padding, em_mask_padding_in_px)
if shared.opts.em_save_masks:
images.save_image(
mask,
shared.opts.em_outdir_masks,
"",
start_seed,
mask_prompt,
shared.opts.samples_format,
info=initial_info,
p=p
)
return mask, mask_success
def update_info(self, info, em_params):
reg_ex = ':\s[0-9a-zA-Z\-\.\s]+'
for param in self.EM_DYNAMIC_PARAMS:
if param in em_params:
info = re.sub(param + reg_ex, '%s: %s' % (param, em_params[param]), info)
return info
##############################
##### Creating new image #####
##############################
def create_new_image(self, p, em_params, init_orig_prompt, changing_model, context, wildcards_generator):
em_params = utils.removeEmptyStringValues(em_params)
if changing_model:
context.restore_original_checkpoint()
self.build_original_prompt(p, init_orig_prompt, em_params, wildcards_generator)
return self.generate_initial_image_with_extra_params(p, em_params)
def build_original_prompt(self, p, init_orig_prompt, em_params, wildcards_generator):
if not shared.opts.em_wildcards_in_original:
return
new_prompt = wildcards_generator.build_prompt(init_orig_prompt)
if new_prompt != init_orig_prompt:
em_params['em_prompt'] = init_orig_prompt
em_params['em_prompt_final'] = new_prompt
p.prompt = new_prompt
def generate_initial_image_with_extra_params(self, p, extra_params):
p.extra_generation_params = p.extra_generation_params or {}
p.extra_generation_params.update(extra_params)
shared.state.job_count += 1
processed = process_images(p)
return processed.images[0], processed.info, True

View File

@ -0,0 +1,211 @@
import gc
import modules.shared as shared
from modules import devices, images
from modules.processing import fix_seed, process_images, StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img
from . import widlcards
from .state import SharedSettingsContext
from .script import EyeMasksCore
class EyeMasksEmbeddedCore(EyeMasksCore):
def execute_process(self, *args):
p, em_enabled = args[:2]
if not em_enabled:
return
p.batch_size = 1
p.do_not_save_grid = True
p.do_not_save_samples = True
def execute_postprocess(self, p, processed,
em_enabled,
em_n_iter,
em_mask_type,
em_mask_prompt,
em_mask_negative_prompt,
em_mask_padding,
em_mask_padding_in_px,
em_mask_steps,
em_include_mask,
em_mask_blur,
em_denoising_strength,
em_cfg_scale,
em_width,
em_height,
em_inpaint_full_res,
em_inpaint_full_res_padding,
em_use_other_model,
em_model
):
if not em_enabled:
return
em_params = {
'em_mask_prompt': em_mask_prompt,
'em_mask_negative_prompt': em_mask_negative_prompt,
'em_mask_type': em_mask_type,
'em_mask_padding': em_mask_padding,
'em_mask_steps': em_mask_steps,
'em_mask_blur': em_mask_blur,
'em_denoising_strength': em_denoising_strength,
'em_cfg_scale': em_cfg_scale,
'em_width': em_width,
'em_height': em_height,
'em_inpaint_full_res': em_inpaint_full_res,
'em_inpaint_full_res_padding': em_inpaint_full_res_padding
}
fix_seed(p)
seed = p.seed
iterations = em_n_iter
initial_info = None
orig_image_info = None
new_img2img_info = None
is_txt2img = isinstance(p, StableDiffusionProcessingTxt2Img)
wildcards_generator_original = widlcards.WildcardsGenerator()
wildcards_generator_mask = widlcards.WildcardsGenerator()
p_em = StableDiffusionProcessingImg2Img(
init_images=[processed.images[0]],
resize_mode=0,
denoising_strength=em_denoising_strength,
mask=None,
mask_blur=em_mask_blur,
inpainting_fill=1,
inpaint_full_res=em_inpaint_full_res,
inpaint_full_res_padding=em_inpaint_full_res_padding,
inpainting_mask_invert=0,
sd_model=p.sd_model,
outpath_samples=p.outpath_samples,
outpath_grids=p.outpath_grids,
prompt=p.prompt,
negative_prompt=p.negative_prompt,
styles=p.styles,
seed=p.seed,
subseed=p.subseed,
subseed_strength=p.subseed_strength,
seed_resize_from_h=p.seed_resize_from_h,
seed_resize_from_w=p.seed_resize_from_w,
sampler_name=p.sampler_name,
n_iter=p.n_iter,
steps=p.steps,
cfg_scale=p.cfg_scale,
width=p.width,
height=p.height,
tiling=p.tiling,
)
p_em.do_not_save_grid = True
p_em.do_not_save_samples = True
init_orig_prompt = p.prompt or ''
initial_info = processed.info
shared.state.job_count = 0
changing_model = em_use_other_model and em_model != 'None'
if changing_model:
em_params['em_mask_model'] = em_model
with SharedSettingsContext(changing_model) as context:
for i in range(len(processed.images)):
orig_image = processed.images[i]
init_image = None
if orig_image.info is not None and 'parameters' in orig_image.info:
orig_image_info = orig_image.info['parameters']
shared.state.job_count += 1
mask, mask_success = self.get_mask(
em_mask_type, em_mask_padding, em_mask_padding_in_px, orig_image,
p_em, seed, em_mask_prompt, initial_info
)
if mask_success:
p_em.image_mask = mask
for n in range(iterations):
devices.torch_gc()
gc.collect()
start_seed = seed + n
mask_prompt = em_mask_prompt
if em_mask_prompt is not None and len(em_mask_prompt.strip()) > 0:
mask_prompt = wildcards_generator_mask.build_prompt(em_mask_prompt)
em_params['em_mask_prompt_final'] = mask_prompt
if init_image is None:
init_image, new_img2img_info, new_image_generated = self.create_new_image(
p_em, em_params, init_orig_prompt, changing_model, context, wildcards_generator_original
)
p_em.seed = start_seed
p_em.init_images = [orig_image]
p_em.prompt = mask_prompt
p_em.negative_prompt = em_mask_negative_prompt
p_em.steps = em_mask_steps
p_em.denoising_strength = em_denoising_strength
p_em.mask_blur = em_mask_blur
p_em.cfg_scale = em_cfg_scale
p_em.width = em_width
p_em.height = em_height
p_em.inpaint_full_res = em_inpaint_full_res
p_em.inpaint_full_res_padding= em_inpaint_full_res_padding
p_em.inpainting_mask_invert = 0
print(f"Processing {n + 1} / {iterations}.")
if changing_model:
context.apply_checkpoint(em_model)
shared.state.job_count += 1
processed_em = process_images(p_em)
lines = new_img2img_info.splitlines(keepends=True)
lines[0] = p.prompt + '\n'
new_img2img_info = ''.join(lines)
if is_txt2img:
initial_info = new_img2img_info
save_prompt = p.prompt
elif not is_txt2img:
initial_info = new_img2img_info
try:
save_prompt = orig_image_info.split('\n')[0]
except Exception as e:
print(e)
save_prompt = orig_image_info
processed.images.append(processed_em.images[0])
if em_include_mask and (n == iterations - 1):
processed.images.append(mask)
shared.state.current_image = processed_em.images[0]
images.save_image(
processed_em.images[0],
p_em.outpath_samples,
"",
start_seed,
save_prompt,
shared.opts.samples_format,
info=self.update_info(initial_info, em_params),
p=p_em
)
devices.torch_gc()
gc.collect()

46
scripts/eyemask/state.py Normal file
View File

@ -0,0 +1,46 @@
import modules.shared as shared
import modules.sd_samplers
import modules.sd_models
import modules.sd_vae
class SharedSettingsContext(object):
def __init__(self, changing_model):
self.changing_model = changing_model
def __enter__(self):
if self.changing_model:
self.CLIP_stop_at_last_layers = shared.opts.CLIP_stop_at_last_layers
self.sd_model_checkpoint = shared.opts.sd_model_checkpoint
self.vae = shared.opts.sd_vae
return self
def __exit__(self, exc_type, exc_value, tb):
if self.changing_model:
shared.opts.data["sd_vae"] = self.vae
shared.opts.data["CLIP_stop_at_last_layers"] = self.CLIP_stop_at_last_layers
self.apply_checkpoint(self.sd_model_checkpoint)
modules.sd_vae.reload_vae_weights()
def apply_checkpoint(self, x):
info = modules.sd_models.get_closet_checkpoint_match(x)
if info is None:
raise RuntimeError(f"Unknown checkpoint: {x}")
modules.sd_models.reload_model_weights(shared.sd_model, info)
def restore_original_checkpoint(self):
self.apply_checkpoint(self.sd_model_checkpoint)
def find_vae(self, name: str):
if name.lower() in ['auto', 'automatic']:
return modules.sd_vae.unspecified
if name.lower() == 'none':
return None
else:
choices = [x for x in sorted(modules.sd_vae.vae_dict, key=lambda x: len(x)) if name.lower().strip() in x.lower()]
if len(choices) == 0:
print(f"No VAE found for {name}; using automatic")
return modules.sd_vae.unspecified
else:
return modules.sd_vae.vae_dict[choices[0]]

189
scripts/eyemask/ui.py Normal file
View File

@ -0,0 +1,189 @@
import datetime
import importlib
import gradio as gr
import modules.sd_models
from modules.ui_components import ToolButton
from . import constants, script, script_embedded, utils, widlcards, state, mask_generator
from modules import shared
class EyeMaskUI():
def __init__(self, eye_mask_script):
self.eye_mask_script = eye_mask_script
self.eye_mask_core = eye_mask_script.eye_mask_core
self.is_embedded = isinstance(self.eye_mask_core, script_embedded.EyeMasksEmbeddedCore)
def restart(self):
# interrupt current image processing
shared.state.interrupt()
# reimport all dynamic packs
importlib.reload(utils)
importlib.reload(state)
importlib.reload(widlcards)
importlib.reload(mask_generator)
# instantiate again core logic
if self.is_embedded:
importlib.reload(script_embedded)
self.eye_mask_script.eye_mask_core = script_embedded.EyeMasksEmbeddedCore()
else:
importlib.reload(script)
self.eye_mask_script.eye_mask_core = script.EyeMasksCore()
# update UI
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
print(f'{constants.script_name} reloaded. Last reload {now}')
return f'<div style="text-align:center; margin-bottom: 8px;">Last reload: {now}</div>'
def get_elem_id_prefix(self):
result = 'em-'
if self.is_embedded:
result += 'emb-'
return result
def get_id(self, id, is_img2img):
return "%s%s-%s" % (self.get_elem_id_prefix(), id, "img2img" if is_img2img else "txt2img")
def render(self, is_img2img):
if self.is_embedded:
with gr.Group():
with gr.Accordion(constants.script_name, open=False):
with gr.Group(elem_id=self.get_id("eye-mask-container", is_img2img)):
result = self.render_inner(is_img2img)
else:
with gr.Blocks():
with gr.Group(elem_id=self.get_id("eye-mask-container", is_img2img)):
result = self.render_inner(is_img2img)
return result
def render_inner(self, is_img2img):
def get_id(id):
return self.get_id(id, is_img2img)
result = []
if self.is_embedded:
with gr.Row():
with gr.Column(scale=2):
em_enabled = gr.Checkbox(elem_id=get_id("enabled"), label="Enable", value=False, visible=True)
result.append(em_enabled)
with gr.Column(scale=1):
em_n_iter = gr.Slider(elem_id=get_id("count"), label="Batch count", minimum=1, maximum=100, step=1, value=1, visible=True)
result.append(em_n_iter)
with gr.Row():
em_mask_prompt = gr.Textbox(
elem_id=get_id("prompt"),
show_label=False,
lines=1,
placeholder="Mask prompt",
visible=True
)
with gr.Row():
em_mask_negative_prompt = gr.Textbox(
elem_id=get_id("negative-prompt"),
show_label=False,
lines=1,
placeholder="Negative mask prompt",
visible=True
)
with gr.Row():
em_mask_type = gr.Radio(
elem_id=get_id("mask-type"),
label="Mask type",
choices=self.eye_mask_core.MASK_TYPES,
value=self.eye_mask_core.MASK_TYPES[0],
type="index"
)
em_info = ToolButton(value="\u2139\uFE0F", elem_id="eye-info-button", full_width=False)
em_info.click(
fn=lambda: '',
_js="() => EyeMaskController.showInfo()",
show_progress=False
)
with gr.Row(elem_id=get_id("mask-type-row")):
with gr.Accordion("Mask Preview", open=False):
with gr.Row():
em_mp_input_image = gr.Image(source="upload", mirror_webcam=False, type="pil")
em_mp_generated_image = gr.Image(label="Mask result", visible=True)
with gr.Row():
def on_generate_mask_click(input_image, em_mask_type):
if input_image is None:
return
mask, mask_success = self.eye_mask_core.generate_mask(input_image, em_mask_type)
return gr.update(value=mask, visible=mask_success, interactive=False)
em_mp_generate_button = gr.Button(value="Generate mask")
em_mp_generate_button.click(fn=on_generate_mask_click, inputs=[em_mp_input_image, em_mask_type], outputs=[em_mp_generated_image])
with gr.Row():
em_mask_padding = gr.Slider(elem_id=get_id("mask-padding"), label="Mask padding (dlib only)", minimum=0, maximum=100, step=1, value=20, visible=True)
em_mask_steps = gr.Slider(elem_id=get_id("mask-steps"), minimum=1, maximum=150, step=1, label="Sampling steps", value=20)
with gr.Row():
em_mask_blur = gr.Slider(elem_id=get_id("mask-blur"), label="Mask blur", minimum=0, maximum=64, step=1, value=4, visible=True)
em_denoising_strength = gr.Slider(elem_id=get_id("denoising-strength"), label="Denoising strength (Inpaint)", minimum=0.0, maximum=1.0, step=0.01, value=0.4, visible=True)
with gr.Row():
em_inpaint_full_res = gr.Checkbox(label="Inpaint at full resolution", value=True, visible=False)
em_inpaint_full_res_padding = gr.Slider(elem_id=get_id("full-res-padding"), label="Inpaint at full resolution padding, pixels", minimum=0, maximum=256, step=4, value=88, visible=True)
em_cfg_scale = gr.Slider(elem_id=get_id("cfg"), minimum=1.0, maximum=30.0, step=0.5, label="CFG Scale", value=7.0)
with gr.Row():
with gr.Column(scale=4):
em_width = gr.Slider(elem_id=get_id("width"), minimum=64, maximum=2048, step=8, label="Width", value=512)
em_height = gr.Slider(elem_id=get_id("height"), minimum=64, maximum=2048, step=8, label="Height", value=512)
with gr.Row():
em_include_mask = gr.Checkbox(elem_id=get_id("include-mask"), label="Include mask", value=True, visible=True)
if not self.is_embedded:
em_redraw_original = gr.Checkbox(elem_id=get_id("redraw-original"), label="Redraw original", value=True, visible=(not is_img2img))
result.append(em_redraw_original)
em_mask_padding_in_px = gr.Checkbox(elem_id=get_id("padding-in-px"), label="Padding in px", value=True, visible=True)
em_use_other_model = gr.Checkbox(elem_id=get_id("use-other-model"), label="Use other model", value=False, visible=True)
with gr.Row():
em_model = gr.Dropdown(
elem_id=get_id('mask-model'),
label="Mask model",
choices=["None"] + list(modules.sd_models.checkpoints_list.keys()),
value="None",
visible=False,
type="value",
)
em_use_other_model.change(
lambda visible: {"visible": visible, "__type__": "update"},
inputs=[em_use_other_model],
outputs=[em_model]
)
with gr.Row():
reload_info = gr.HTML()
with gr.Row():
reload_button = gr.Button(
elem_id=get_id("reload-extension"),
value="Reload Extension",
full_width=True,
visible=utils.get_opt("em_dev_mode")
)
reload_button.click(
fn=self.restart,
_js="() => toastr.success('Extension reloaded')",
outputs=[reload_info],
show_progress=False
)
return result + [
em_mask_type,
em_mask_prompt,
em_mask_negative_prompt,
em_mask_padding,
em_mask_padding_in_px,
em_mask_steps,
em_include_mask,
em_mask_blur,
em_denoising_strength,
em_cfg_scale,
em_width,
em_height,
em_inpaint_full_res,
em_inpaint_full_res_padding,
em_use_other_model,
em_model
]

39
scripts/eyemask/utils.py Normal file
View File

@ -0,0 +1,39 @@
import numpy as np
import importlib.util
from modules import shared
def get_opt(key, default=False):
try:
return shared.opts.__getattr__(key) or default
except Exception:
return default
def expand_polygon(points, distance):
center = np.mean(points, axis=0)
new_points = []
for point in points:
vec = point - center
vec = vec / np.linalg.norm(vec)
new_points.append(point + vec * distance)
return new_points
def calculate_distance(p1, p2):
x1, y1 = p1
x2, y2 = p2
return ((x1 - x2)**2 + (y1 - y2)**2)**0.5
def load_module_from_file(module_name, file_path):
spec = importlib.util.spec_from_file_location(module_name, file_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def index(list, element):
if element not in list:
return -1
return list.index(element)
def removeEmptyStringValues(dct):
return { k: v for k, v in dct.items() if v }

View File

@ -0,0 +1,45 @@
import os
import sys
import re
import random
from .constants import script_wildcards_dir
class WildcardsGenerator():
def __init__(self):
self.wildcards_warned_about_files = {}
self.wildcard_indexes = {}
def build_prompt(self, prompt):
if re.search("_{2}[a-z_]+_{2}", prompt):
return "".join(self.replace_wildcard(chunk) for chunk in prompt.split("__"))
return prompt
def get_index(self, file_name, max_index):
if not file_name in self.wildcard_indexes or self.wildcard_indexes[file_name] == max_index:
self.wildcard_indexes[file_name] = 0
else:
self.wildcard_indexes[file_name] += 1
return self.wildcard_indexes[file_name]
def replace_wildcard(self, text):
if " " in text or len(text) == 0:
return text
replacement_file = os.path.join(script_wildcards_dir, f"{text}.txt")
if os.path.exists(replacement_file):
with open(replacement_file, encoding="utf8") as f:
lines = f.read().splitlines()
if text[-5:] == '_each':
return lines[self.get_index(text, len(lines) - 1)]
else:
return random.Random().choice(lines)
else:
if replacement_file not in self.wildcards_warned_about_files:
print(f"File {replacement_file} not found for the __{text}__ wildcard.", file=sys.stderr)
self.wildcards_warned_about_files[replacement_file] = 1
return text

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

1
style.css Normal file

File diff suppressed because one or more lines are too long

1
style.css.map Normal file
View File

@ -0,0 +1 @@
{"version":3,"sourceRoot":"","sources":["style/_mixins.scss","style/_main.scss","style/_dialog.scss","style/_toastr.scss"],"names":[],"mappings":"AAmBgB,oMAEQ,OCpBc,MDelC,wHASY,cClBmB,KDS/B,oGASY,WCdc,KDK1B,gHASY,cCViB,KDUjB,QCViB,ODUjB,UCViB,KDC7B,4HASY,OCJoB,iBClBpC,mBACI,kBACA,SACA,+BACA,gCAGJ,wBACI,eACA,MACA,OACA,WACA,YACA,aACA,qCAGJ,6BACI,aACA,eACA,QACA,OACA,QACA,YACA,cACA,YACA,YACA,WACA,kBACA,yBACA,yBACA,4CACA,gBACA,aAGJ,uCACI,eACA,gBACA,UACA,QAGJ,sDACI,iBAGJ,4DACI,gBAGJ,kEACI,kBAGJ,iCACI,kBAGJ,oCACI,SACA,UACA,eACA,mBAGJ,iCACI,iBAGJ,wCACI,cACA,mBACA,iBACA,eACA,mBACA,0BACA,WAGJ,+CACI,mBAGJ,kCACI,6BACA,gCACA,aACA,iBACA,kBAGJ,gDACI,cAGJ,qDACI,UACA,WClGJ,aACI,iBAEJ,eACI,yBACA,qBAEJ,sCAEI,WAEJ,uBACI,WACA,qBAEJ,oBACI,kBACA,aACA,WACA,YACA,eACA,iBACA,WACA,iCACA,yBACA,WACA,+DACA,yBACA,cAEJ,oDAEI,WACA,qBACA,eACA,WACA,+DACA,yBAEJ,yBACI,YACA,WACA,WAKJ,0BACI,UACA,eACA,yBACA,SACA,wBAEJ,kBACI,MACA,QACA,WAEJ,qBACI,SACA,QACA,WAEJ,sBACI,MACA,QACA,WAEJ,yBACI,SACA,QACA,WAEJ,gBACI,SACA,UAEJ,iBACI,SACA,WAEJ,oBACI,WACA,YAEJ,mBACI,YACA,UAEJ,iBACI,eACA,eACA,oBAGJ,mBACI,2BACA,8BACA,sBAEJ,qBACI,kBACA,oBACA,gBACA,eACA,4BACA,YACA,mCACA,sCACA,8BACA,gCACA,4BACA,8BACA,iCACA,yBACA,WACA,WACA,+DACA,yBAEJ,yBACI,cACA,4BACA,sCAEJ,2BACI,8BACA,iCACA,yBACA,UACA,gEACA,0BACA,eAEJ,6BACI,0wBAEJ,8BACI,kzBAEJ,gCACI,sgBAEJ,gCACI,0uBAEJ,+EAEI,YACA,iBACA,kBAEJ,uFAEI,UACA,iBACA,kBAEJ,OACI,yBAEJ,eACI,yBAEJ,aACI,yBAEJ,YACI,yBAEJ,eACI,yBAEJ,gBACI,kBACA,OACA,SACA,WACA,sBACA,WACA,+DACA,yBAGJ,kCACI,qBACI,yBACA,WAEJ,yBACI,yBAEJ,qCACI,aACA,WAEJ,0CACI,YACA,YAGR,wDACI,qBACI,yBACA,WAEJ,yBACI,yBAEJ,qCACI,aACA,WAEJ,0CACI,YACA,YAGR,wDACI,qBACI,4BACA,WAEJ,yBACI","file":"style.css"}

103
style/_dialog.scss Normal file
View File

@ -0,0 +1,103 @@
/***** Dialog styles *****/
#dialogs-container {
position: relative;
height: 0;
background-color: transparent;
transition: background-color 0.2s;
}
#dialogs-container.open {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1000;
background-color: rgba(41, 111, 180, 0.3);
}
#dialogs-container > div.modal {
display: none;
position: fixed;
top: 30%;
left: 0;
right: 0;
width: 300px;
max-width: 90%;
height: auto;
margin: auto;
color: #000;
border-radius: 8px;
border: 1px solid #b1dde4;
background-color: rgba(241, 248, 255, 1);
box-shadow: 0 0 35px 3px rgba(41, 111, 180, 0.5);
overflow: hidden;
z-index: 2000;
}
#dialogs-container > div.modal.modal-big {
font-size: 14px;
max-width: 700px;
width: 95%;
top: 10%;
}
#dialogs-container > div.modal.modal-big .modal-content {
max-height: 350px;
}
#dialogs-container > div.modal.modal-big .modal-footer button {
min-width: 150px;
}
#dialogs-container .modal-header, #dialogs-container .modal-footer {
text-align: center;
}
#dialogs-container .modal-header {
padding: 10px 10px;
}
#dialogs-container .modal-header h5 {
margin: 0;
padding: 0;
font-size: 16px;
font-weight: normal;
}
#dialogs-container .modal-footer {
padding: 5px 10px;
}
#dialogs-container .modal-footer button {
min-width: 40%;
border-radius: 30px;
padding: 5px 15px;
margin: 5px 7px;
background: #296fb4;
transition: all 0.2s linear;
color: white;
}
#dialogs-container .modal-footer button.danger {
background: #df4c73;
}
#dialogs-container .modal-content {
border-top: 1px solid #b1dde4;
border-bottom: 1px solid #b1dde4;
padding: 20px;
max-height: 250px;
overflow-y: scroll;
}
#dialogs-container .modal-content a.simple-link {
color: #296fb4;
}
#dialogs-container .modal-content::-webkit-scrollbar {
width: 5px;
height: 5px;
}

24
style/_main.scss Normal file
View File

@ -0,0 +1,24 @@
@include select('eye-mask-container', (
'.gr-button-tool': (
margin: 0.55em
)
));
@include select('negative-prompt', (
margin-bottom: 15px
));
@include select('mask-model', (
margin-top: 10px
));
@include select('mask-type-row', (
margin-bottom: 20px,
padding: 0 12px,
font-size: 12px
));
@include select('reload-extension', (
margin: 10px 12px 0 12px
));

30
style/_mixins.scss Normal file
View File

@ -0,0 +1,30 @@
@mixin select($id, $rules) {
$selectors: (
'#em-#{ $id }-txt2img',
'#em-#{ $id }-img2img',
'#em-emb-#{ $id }-txt2img',
'#em-emb-#{ $id }-img2img'
);
$selector-string: '';
@each $selector in $selectors {
$selector-string: #{$selector-string}#{$selector}', ' ;
}
#{ $selector-string } {
@each $property, $value in $rules {
@if type-of($value) == map {
#{ $property } {
@each $subproperty, $subvalue in $value {
#{ $subproperty }: $subvalue;
}
}
} @else {
#{ $property }: $value;
}
}
}
}

231
style/_toastr.scss Normal file
View File

@ -0,0 +1,231 @@
/***** Toastr styles *****/
.toast-title {
font-weight: bold;
}
.toast-message {
-ms-word-wrap: break-word;
word-wrap: break-word;
}
.toast-message a,
.toast-message label {
color: #FFFFFF;
}
.toast-message a:hover {
color: #CCCCCC;
text-decoration: none;
}
.toast-close-button {
position: relative;
right: -0.3em;
top: -0.3em;
float: right;
font-size: 20px;
font-weight: bold;
color: #FFFFFF;
-webkit-text-shadow: 0 1px 0 #ffffff;
text-shadow: 0 1px 0 #ffffff;
opacity: 0.8;
-ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80);
filter: alpha(opacity=80);
line-height: 1;
}
.toast-close-button:hover,
.toast-close-button:focus {
color: #000000;
text-decoration: none;
cursor: pointer;
opacity: 0.4;
-ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40);
filter: alpha(opacity=40);
}
.rtl .toast-close-button {
left: -0.3em;
float: left;
right: 0.3em;
}
/*Additional properties for button version
iOS requires the button element instead of an anchor tag.
If you want the anchor version, it requires `href="#"`.*/
button.toast-close-button {
padding: 0;
cursor: pointer;
background: transparent;
border: 0;
-webkit-appearance: none;
}
.toast-top-center {
top: 0;
right: 0;
width: 100%;
}
.toast-bottom-center {
bottom: 0;
right: 0;
width: 100%;
}
.toast-top-full-width {
top: 0;
right: 0;
width: 100%;
}
.toast-bottom-full-width {
bottom: 0;
right: 0;
width: 100%;
}
.toast-top-left {
top: 12px;
left: 12px;
}
.toast-top-right {
top: 12px;
right: 12px;
}
.toast-bottom-right {
right: 12px;
bottom: 12px;
}
.toast-bottom-left {
bottom: 12px;
left: 12px;
}
#toast-container {
position: fixed;
z-index: 999999;
pointer-events: none;
/*overrides*/
}
#toast-container * {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
#toast-container > div {
position: relative;
pointer-events: auto;
overflow: hidden;
margin: 0 0 6px;
padding: 15px 15px 15px 50px;
width: 300px;
-moz-border-radius: 3px 3px 3px 3px;
-webkit-border-radius: 3px 3px 3px 3px;
border-radius: 3px 3px 3px 3px;
background-position: 15px center;
background-repeat: no-repeat;
-moz-box-shadow: 0 0 12px #999999;
-webkit-box-shadow: 0 0 12px #999999;
box-shadow: 0 0 12px #999999;
color: #FFFFFF;
opacity: 0.8;
-ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80);
filter: alpha(opacity=80);
}
#toast-container > div.rtl {
direction: rtl;
padding: 15px 50px 15px 15px;
background-position: right 15px center;
}
#toast-container > div:hover {
-moz-box-shadow: 0 0 12px #000000;
-webkit-box-shadow: 0 0 12px #000000;
box-shadow: 0 0 12px #000000;
opacity: 1;
-ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
filter: alpha(opacity=100);
cursor: pointer;
}
#toast-container > .toast-info {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=") !important;
}
#toast-container > .toast-error {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=") !important;
}
#toast-container > .toast-success {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==") !important;
}
#toast-container > .toast-warning {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=") !important;
}
#toast-container.toast-top-center > div,
#toast-container.toast-bottom-center > div {
width: 300px;
margin-left: auto;
margin-right: auto;
}
#toast-container.toast-top-full-width > div,
#toast-container.toast-bottom-full-width > div {
width: 96%;
margin-left: auto;
margin-right: auto;
}
.toast {
background-color: #030303;
}
.toast-success {
background-color: #51A351;
}
.toast-error {
background-color: #BD362F;
}
.toast-info {
background-color: #2F96B4;
}
.toast-warning {
background-color: #F89406;
}
.toast-progress {
position: absolute;
left: 0;
bottom: 0;
height: 4px;
background-color: #000000;
opacity: 0.4;
-ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40);
filter: alpha(opacity=40);
}
/*Responsive Design*/
@media all and (max-width: 240px) {
#toast-container > div {
padding: 8px 8px 8px 50px;
width: 11em;
}
#toast-container > div.rtl {
padding: 8px 50px 8px 8px;
}
#toast-container .toast-close-button {
right: -0.2em;
top: -0.2em;
}
#toast-container .rtl .toast-close-button {
left: -0.2em;
right: 0.2em;
}
}
@media all and (min-width: 241px) and (max-width: 480px) {
#toast-container > div {
padding: 8px 8px 8px 50px;
width: 18em;
}
#toast-container > div.rtl {
padding: 8px 50px 8px 8px;
}
#toast-container .toast-close-button {
right: -0.2em;
top: -0.2em;
}
#toast-container .rtl .toast-close-button {
left: -0.2em;
right: 0.2em;
}
}
@media all and (min-width: 481px) and (max-width: 768px) {
#toast-container > div {
padding: 15px 15px 15px 50px;
width: 25em;
}
#toast-container > div.rtl {
padding: 15px 50px 15px 15px;
}
}

6
style/style.scss Normal file
View File

@ -0,0 +1,6 @@
@charset "UTF-8";
@import "mixins";
@import "main";
@import "dialog";
@import "toastr";

14
wildcards/elements.txt Normal file
View File

@ -0,0 +1,14 @@
Earth
Water
Wind
Fire
Thunder
Ice
Force
Time
Flower
Shadow
Light
Darkness
Metal
Moon

View File

@ -0,0 +1,14 @@
Earth
Water
Wind
Fire
Thunder
Ice
Force
Time
Flower
Shadow
Light
Darkness
Metal
Moon

3
wildcards/eyes.txt Normal file
View File

@ -0,0 +1,3 @@
red
green
blue

3
wildcards/eyes_each.txt Normal file
View File

@ -0,0 +1,3 @@
red
green
blue

4
wildcards/faces.txt Normal file
View File

@ -0,0 +1,4 @@
Brad Pitt
George Clooney
Jim Carrey
Johnny Depp

4
wildcards/faces_each.txt Normal file
View File

@ -0,0 +1,4 @@
Brad Pitt
George Clooney
Jim Carrey
Johnny Depp

View File