configurable stream resolution

This commit is contained in:
Devaev Maxim 2018-08-24 07:13:40 +03:00
parent 0468100bba
commit 52d2b8a315
12 changed files with 383 additions and 41 deletions

View File

@ -36,14 +36,13 @@ kvmd:
init_restart_after: 1.0 init_restart_after: 1.0
shutdown_delay: 10.0 shutdown_delay: 10.0
size: resolutions:
width: 800 - 800x600 - 720x576
height: 600
cmd: cmd:
- "/usr/bin/mjpg_streamer" - "/usr/bin/mjpg_streamer"
- "-i" - "-i"
- "input_uvc.so -d /dev/kvmd-streamer -e 2 -t pal -y -n -r 720x576" - "input_uvc.so -d /dev/kvmd-streamer -e 2 -t pal -y -n -r {resolution}"
- "-o" - "-o"
- "output_http.so -l localhost -p 8082" - "output_http.so -l localhost -p 8082"

View File

@ -49,8 +49,7 @@ def main() -> None:
sync_delay=float(config["streamer"]["sync_delay"]), sync_delay=float(config["streamer"]["sync_delay"]),
init_delay=float(config["streamer"]["init_delay"]), init_delay=float(config["streamer"]["init_delay"]),
init_restart_after=float(config["streamer"]["init_restart_after"]), init_restart_after=float(config["streamer"]["init_restart_after"]),
width=int(config["streamer"]["size"]["width"]), resolutions=config["streamer"]["resolutions"],
height=int(config["streamer"]["size"]["height"]),
cmd=list(map(str, config["streamer"]["cmd"])), cmd=list(map(str, config["streamer"]["cmd"])),
loop=loop, loop=loop,
) )

View File

@ -128,6 +128,7 @@ class Server: # pylint: disable=too-many-instance-attributes
self.__system_tasks: List[asyncio.Task] = [] self.__system_tasks: List[asyncio.Task] = []
self.__reset_streamer = False self.__reset_streamer = False
self.__streamer_resolution = streamer.get_current_resolution()
def run(self, host: str, port: int) -> None: def run(self, host: str, port: int) -> None:
self.__hid.start() self.__hid.start()
@ -148,6 +149,7 @@ class Server: # pylint: disable=too-many-instance-attributes
app.router.add_post("/msd/write", self.__msd_write_handler) app.router.add_post("/msd/write", self.__msd_write_handler)
app.router.add_get("/streamer", self.__streamer_state_handler) app.router.add_get("/streamer", self.__streamer_state_handler)
app.router.add_post("/streamer/set_params", self.__streamer_set_params_handler)
app.router.add_post("/streamer/reset", self.__streamer_reset_handler) app.router.add_post("/streamer/reset", self.__streamer_reset_handler)
app.on_shutdown.append(self.__on_shutdown) app.on_shutdown.append(self.__on_shutdown)
@ -301,6 +303,18 @@ class Server: # pylint: disable=too-many-instance-attributes
async def __streamer_state_handler(self, _: aiohttp.web.Request) -> aiohttp.web.Response: async def __streamer_state_handler(self, _: aiohttp.web.Request) -> aiohttp.web.Response:
return _json(self.__streamer.get_state()) return _json(self.__streamer.get_state())
@_wrap_exceptions_for_web("Can't set stream params")
async def __streamer_set_params_handler(self, request: aiohttp.web.Request) -> aiohttp.web.Response:
resolution = request.query.get("resolution")
if resolution:
if resolution in self.__streamer.get_available_resolutions():
if resolution != self.__streamer_resolution:
self.__streamer_resolution = resolution
self.__reset_streamer = True
else:
raise BadRequest("Unknown resolution %r" % (resolution))
return _json()
async def __streamer_reset_handler(self, _: aiohttp.web.Request) -> aiohttp.web.Response: async def __streamer_reset_handler(self, _: aiohttp.web.Request) -> aiohttp.web.Response:
self.__reset_streamer = True self.__reset_streamer = True
return _json() return _json()
@ -344,17 +358,20 @@ class Server: # pylint: disable=too-many-instance-attributes
cur = len(self.__sockets) cur = len(self.__sockets)
if prev == 0 and cur > 0: if prev == 0 and cur > 0:
if not self.__streamer.is_running(): if not self.__streamer.is_running():
await self.__streamer.start() await self.__streamer.start(self.__streamer_resolution)
await self.__broadcast_event("streamer_state", **self.__streamer.get_state())
elif prev > 0 and cur == 0: elif prev > 0 and cur == 0:
shutdown_at = time.time() + self.__streamer_shutdown_delay shutdown_at = time.time() + self.__streamer_shutdown_delay
elif prev == 0 and cur == 0 and time.time() > shutdown_at: elif prev == 0 and cur == 0 and time.time() > shutdown_at:
if self.__streamer.is_running(): if self.__streamer.is_running():
await self.__streamer.stop() await self.__streamer.stop()
await self.__broadcast_event("streamer_state", **self.__streamer.get_state())
if self.__reset_streamer: if self.__reset_streamer:
if self.__streamer.is_running(): if self.__streamer.is_running():
await self.__streamer.stop() await self.__streamer.stop()
await self.__streamer.start(no_init_restart=True) await self.__streamer.start(self.__streamer_resolution, no_init_restart=True)
await self.__broadcast_event("streamer_state", **self.__streamer.get_state())
self.__reset_streamer = False self.__reset_streamer = False
prev = cur prev = cur

View File

@ -1,6 +1,8 @@
import asyncio import asyncio
import asyncio.subprocess import asyncio.subprocess
from collections import OrderedDict as odict
from typing import List from typing import List
from typing import Dict from typing import Dict
from typing import Optional from typing import Optional
@ -20,8 +22,7 @@ class Streamer: # pylint: disable=too-many-instance-attributes
init_delay: float, init_delay: float,
init_restart_after: float, init_restart_after: float,
width: int, resolutions: List[str],
height: int,
cmd: List[str], cmd: List[str],
loop: asyncio.AbstractEventLoop, loop: asyncio.AbstractEventLoop,
@ -33,17 +34,25 @@ class Streamer: # pylint: disable=too-many-instance-attributes
self.__init_delay = init_delay self.__init_delay = init_delay
self.__init_restart_after = init_restart_after self.__init_restart_after = init_restart_after
self.__width = width self.__resolutions = odict([
self.__height = height (display, (real or display))
for (display, real) in [
(tuple(map(str.lower, map(str.strip, resolution.split("-", maxsplit=1)))) + ("",))[:2]
for resolution in resolutions
]
])
self.__resolution = list(self.__resolutions)[0]
self.__cmd = cmd self.__cmd = cmd
self.__loop = loop self.__loop = loop
self.__proc_task: Optional[asyncio.Task] = None self.__proc_task: Optional[asyncio.Task] = None
async def start(self, no_init_restart: bool=False) -> None: async def start(self, resolution: str, no_init_restart: bool=False) -> None:
logger = get_logger() logger = get_logger()
logger.info("Starting streamer ...") logger.info("Starting streamer ...")
assert resolution in self.__resolutions, (resolution, self.__resolutions)
self.__resolution = resolution
await self.__inner_start() await self.__inner_start()
if self.__init_restart_after > 0.0 and not no_init_restart: if self.__init_restart_after > 0.0 and not no_init_restart:
logger.info("Stopping streamer to restart ...") logger.info("Stopping streamer to restart ...")
@ -58,13 +67,22 @@ class Streamer: # pylint: disable=too-many-instance-attributes
def is_running(self) -> bool: def is_running(self) -> bool:
return bool(self.__proc_task) return bool(self.__proc_task)
def get_current_resolution(self) -> str:
return self.__resolution
def get_available_resolutions(self) -> List[str]:
return list(self.__resolutions)
def get_state(self) -> Dict: def get_state(self) -> Dict:
(width, height) = tuple(map(int, self.__resolution.split("x")))
return { return {
"is_running": self.is_running(), "is_running": self.is_running(),
"size": { "size": {
"width": self.__width, "width": width,
"height": self.__height, "height": height,
}, },
"resolution": self.__resolution,
"resolutions": list(self.__resolutions),
} }
async def cleanup(self) -> None: async def cleanup(self) -> None:
@ -100,7 +118,7 @@ class Streamer: # pylint: disable=too-many-instance-attributes
while True: # pylint: disable=too-many-nested-blocks while True: # pylint: disable=too-many-nested-blocks
proc: Optional[asyncio.subprocess.Process] = None # pylint: disable=no-member proc: Optional[asyncio.subprocess.Process] = None # pylint: disable=no-member
try: try:
cmd = [part.format(width=self.__width, height=self.__height) for part in self.__cmd] cmd = [part.format(resolution=self.__resolutions[self.__resolution]) for part in self.__cmd]
proc = await asyncio.create_subprocess_exec( proc = await asyncio.create_subprocess_exec(
*cmd, *cmd,
stdout=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE,

View File

@ -36,14 +36,15 @@ kvmd:
init_restart_after: 1.0 init_restart_after: 1.0
shutdown_delay: 10.0 shutdown_delay: 10.0
size: resolutions:
width: 800 - 640x480
height: 600 - 800x600
- 1024x768
cmd: cmd:
- "/usr/bin/mjpg_streamer" - "/usr/bin/mjpg_streamer"
- "-i" - "-i"
- "input_uvc.so -d /dev/kvmd-streamer -e 2 -y -n -r {width}x{height}" - "input_uvc.so -d /dev/kvmd-streamer -e 2 -y -n -r {resolution}"
- "-o" - "-o"
- "output_http.so -l 0.0.0.0 -p 8082" - "output_http.so -l 0.0.0.0 -p 8082"

View File

@ -91,7 +91,7 @@ div.ctl-dropdown-content div.buttons-row {
padding: 0; padding: 0;
font-size: 0; font-size: 0;
} }
div.ctl-dropdown-content button { div.ctl-dropdown-content button, select {
box-shadow: none; box-shadow: none;
border: none; border: none;
color: var(--fg-color-normal); color: var(--fg-color-normal);
@ -105,26 +105,48 @@ div.ctl-dropdown-content button {
outline: none; outline: none;
cursor: pointer; cursor: pointer;
} }
div.ctl-dropdown-content button:enabled:hover { div.ctl-dropdown-content button:enabled:hover, select:enabled:hover {
color: var(--fg-color-intensive); color: var(--fg-color-intensive);
background-color: var(--bg-color-dark) !important; background-color: var(--bg-color-dark) !important;
} }
div.ctl-dropdown-content button:disabled { div.ctl-dropdown-content button:disabled, select:disabled {
color: var(--fg-color-inactive); color: var(--fg-color-inactive);
cursor: default; cursor: default;
} }
div.ctl-dropdown-content button:active { div.ctl-dropdown-content button:active, select:active {
color: var(--fg-color-selected) !important; color: var(--fg-color-selected) !important;
} }
div.ctl-dropdown-content button.row50 { div.ctl-dropdown-content select {
-webkit-appearance: button;
-moz-appearance: button;
appearance: button;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
background-image: url("../svg/select-arrow-normal.svg");
background-position: center right;
background-repeat: no-repeat;
}
div.ctl-dropdown-content select:enabled:hover {
background-image: url("../svg/select-arrow-intensive.svg") !important;
}
div.ctl-dropdown-content select:disabled {
background-image: url("../svg/select-arrow-inactive.svg") !important;
}
div.ctl-dropdown-content select:active {
color: var(--fg-color-intensive) !important;
background-color: var(--bg-color-dark) !important;
background-image: url("../svg/select-arrow-intensive.svg") !important;
}
div.ctl-dropdown-content .row50 {
display: inline-block; display: inline-block;
width: 50%; width: 50%;
} }
div.ctl-dropdown-content button.row25 { div.ctl-dropdown-content .row25 {
display: inline-block; display: inline-block;
width: 25%; width: 25%;
} }
div.ctl-dropdown-content button.row50:not(:first-child), button.row25:not(:first-child) { div.ctl-dropdown-content .row50:not(:first-child), .row25:not(:first-child) {
border-left: var(--dark-border); border-left: var(--dark-border);
} }
div.ctl-dropdown-content hr { div.ctl-dropdown-content hr {

View File

@ -35,21 +35,26 @@ div.stream-box-mouse-enabled {
cursor: url("../svg/stream-mouse-cursor.svg"), pointer; cursor: url("../svg/stream-mouse-cursor.svg"), pointer;
} }
div#stream-size { div.stream-params {
-webkit-user-select: text; -webkit-user-select: text;
-moz-user-select: text; -moz-user-select: text;
user-select: text; user-select: text;
font-size: 12px; font-size: 12px;
margin: 5px 15px 5px 15px; margin: 5px 15px 5px 15px;
} }
div#stream-size span#stream-size-counter {
div.stream-params select#stream-resolution-select {
margin: 8px 0 8px 0;
} }
div#stream-size div#stream-size-slider-box {
div.stream-params span#stream-size-value {
}
div.stream-params div#stream-size-slider-box {
margin-top: 5px; margin-top: 5px;
display: flex; display: flex;
} }
@supports (-webkit-appearance:none) { @supports (-webkit-appearance:none) {
div#stream-size div#stream-size-slider-box input[type=range] { div.stream-params div#stream-size-slider-box input[type=range] {
cursor: pointer; cursor: pointer;
outline: none; outline: none;
width: 100%; width: 100%;
@ -60,7 +65,7 @@ div#stream-size div#stream-size-slider-box {
} }
} }
@supports not (-webkit-appearance:none) { @supports not (-webkit-appearance:none) {
div#stream-size div#stream-size-slider-box input[type=range] { div.stream-params div#stream-size-slider-box input[type=range] {
cursor: pointer; cursor: pointer;
outline: none; outline: none;
width: 100%; width: 100%;
@ -69,12 +74,12 @@ div#stream-size div#stream-size-slider-box {
margin-right: 0; margin-right: 0;
} }
} }
div#stream-size div#stream-size-slider-box input[type=range]::-webkit-slider-runnable-track { div.stream-params div#stream-size-slider-box input[type=range]::-webkit-slider-runnable-track {
height: 5px; height: 5px;
background: var(--bg-color-light); background: var(--bg-color-light);
border-radius: 3px; border-radius: 3px;
} }
div#stream-size div#stream-size-slider-box input[type=range]::-webkit-slider-thumb { div.stream-params div#stream-size-slider-box input[type=range]::-webkit-slider-thumb {
border: var(--intensive-border); border: var(--intensive-border);
height: 18px; height: 18px;
width: 18px; width: 18px;
@ -83,12 +88,12 @@ div#stream-size div#stream-size-slider-box input[type=range]::-webkit-slider-thu
-webkit-appearance: none; -webkit-appearance: none;
margin-top: -7px; margin-top: -7px;
} }
div#stream-size div#stream-size-slider-box input[type=range]::-moz-range-track { div.stream-params div#stream-size-slider-box input[type=range]::-moz-range-track {
height: 5px; height: 5px;
background: var(--bg-color-light); background: var(--bg-color-light);
border-radius: 3px; border-radius: 3px;
} }
div#stream-size div#stream-size-slider-box input[type=range]::-moz-range-thumb { div.stream-params div#stream-size-slider-box input[type=range]::-moz-range-thumb {
border: var(--intensive-border); border: var(--intensive-border);
height: 18px; height: 18px;
width: 18px; width: 18px;

View File

@ -70,8 +70,15 @@
<button id="show-stream-button">&bull; Show stream</button> <button id="show-stream-button">&bull; Show stream</button>
<button disabled id="stream-reset-button">&bull; Reset stream</button> <button disabled id="stream-reset-button">&bull; Reset stream</button>
<hr> <hr>
<div data-dont-hide-menu id="stream-size"> <div data-dont-hide-menu class="stream-params">
Stream size: <span id="stream-size-counter">100%</span> Resolution:
<select disabled data-dont-hide-menu id="stream-resolution-select">
<option>640x480</option>
</select>
</div>
<hr>
<div data-dont-hide-menu class="stream-params">
Stream size: <span id="stream-size-value">100%</span>
<div id="stream-size-slider-box"> <div id="stream-size-slider-box">
<input id="stream-size-slider" type="range" min="50" max="150" value="100" step="10" /> <input id="stream-size-slider" type="range" min="50" max="150" value="100" step="10" />
</div> </div>

View File

@ -4,6 +4,10 @@ function Stream(ui) {
/********************************************************************************/ /********************************************************************************/
var __prev_state = false; var __prev_state = false;
var __resolution = "640x480";
var __resolutions = ["640x480"];
var __normal_size = {width: 640, height: 480}; var __normal_size = {width: 640, height: 480};
var __size_factor = 1; var __size_factor = 1;
@ -11,6 +15,7 @@ function Stream(ui) {
$("stream-led").title = "Stream inactive"; $("stream-led").title = "Stream inactive";
$("stream-reset-button").onclick = __clickResetButton; $("stream-reset-button").onclick = __clickResetButton;
$("stream-resolution-select").onchange = __changeResolution;
$("stream-size-slider").oninput = __resize; $("stream-size-slider").oninput = __resize;
$("stream-size-slider").onchange = __resize; $("stream-size-slider").onchange = __resize;
@ -19,6 +24,8 @@ function Stream(ui) {
/********************************************************************************/ /********************************************************************************/
// XXX: In current implementation we don't need this event because Stream() has own state poller
var __startPoller = function() { var __startPoller = function() {
var http = tools.makeRequest("GET", "/streamer/?action=snapshot", function() { var http = tools.makeRequest("GET", "/streamer/?action=snapshot", function() {
if (http.readyState === 2 || http.readyState === 4) { if (http.readyState === 2 || http.readyState === 4) {
@ -33,6 +40,7 @@ function Stream(ui) {
$("stream-led").className = "led-off"; $("stream-led").className = "led-off";
$("stream-led").title = "Stream inactive"; $("stream-led").title = "Stream inactive";
$("stream-reset-button").disabled = true; $("stream-reset-button").disabled = true;
$("stream-resolution-select").disabled = true;
} else if (!__prev_state) { } else if (!__prev_state) {
__refreshImage(); __refreshImage();
__prev_state = true; __prev_state = true;
@ -44,7 +52,7 @@ function Stream(ui) {
} }
} }
}); });
setTimeout(__startPoller, 2000); setTimeout(__startPoller, 1500);
}; };
var __clickResetButton = function() { var __clickResetButton = function() {
@ -58,9 +66,23 @@ function Stream(ui) {
}); });
}; };
var __changeResolution = function() {
var resolution = $("stream-resolution-select").value;
if (__resolution != resolution) {
$("stream-resolution-select").disabled = true;
var http = tools.makeRequest("POST", "/kvmd/streamer/set_params?resolution=" + resolution, function() {
if (http.readyState === 4) {
if (http.status !== 200) {
alert("Can't change stream:", http.responseText);
}
}
});
}
};
var __resize = function() { var __resize = function() {
var percent = $("stream-size-slider").value; var percent = $("stream-size-slider").value;
$("stream-size-counter").innerHTML = percent + "%"; $("stream-size-value").innerHTML = percent + "%";
__size_factor = percent / 100; __size_factor = percent / 100;
__applySizeFactor(); __applySizeFactor();
}; };
@ -75,7 +97,25 @@ function Stream(ui) {
var __refreshImage = function() { var __refreshImage = function() {
var http = tools.makeRequest("GET", "/kvmd/streamer", function() { var http = tools.makeRequest("GET", "/kvmd/streamer", function() {
if (http.readyState === 4 && http.status === 200) { if (http.readyState === 4 && http.status === 200) {
__normal_size = JSON.parse(http.responseText).result.size; var result = JSON.parse(http.responseText).result;
if (__resolutions != result.resolutions) {
tools.info("Resolutions list changed:", result.resolutions);
$("stream-resolution-select").innerHTML = "";
result.resolutions.forEach(function(resolution) {
$("stream-resolution-select").innerHTML += "<option value=\"" + resolution + "\">" + resolution + "</option>";
});
$("stream-resolution-select").disabled = (result.resolutions.length == 1);
__resolutions = result.resolutions;
}
if (__resolution != result.resolution) {
tools.info("Resolution changed:", result.resolution);
document.querySelector("#stream-resolution-select [value=\"" + result.resolution + "\"]").selected = true;
__resolution = result.resolution;
}
__normal_size = result.size;
__applySizeFactor(); __applySizeFactor();
$("stream-image").src = "/streamer/?action=stream&time=" + new Date().getTime(); $("stream-image").src = "/streamer/?action=stream&time=" + new Date().getTime();
} }

View File

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24"
height="31.999998"
viewBox="0 0 6.3500001 8.4666662"
version="1.1"
id="svg8"
inkscape:version="0.92.2 2405546, 2018-03-11"
sodipodi:docname="select-arrow-inactive.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="11.2"
inkscape:cx="27.151934"
inkscape:cy="16.615415"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-width="1920"
inkscape:window-height="1020"
inkscape:window-x="0"
inkscape:window-y="30"
inkscape:window-maximized="1"
units="px" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-8.8745959,-36.821965)">
<path
sodipodi:type="star"
style="opacity:1;fill:#6c7481;fill-opacity:1;stroke:none;stroke-width:2.64583325;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:75.59055328;stroke-opacity:1;paint-order:normal"
id="path4749"
sodipodi:sides="3"
sodipodi:cx="12.049596"
sodipodi:cy="40.702518"
sodipodi:r1="1.411111"
sodipodi:r2="0.70555568"
sodipodi:arg1="1.5707963"
sodipodi:arg2="2.6179939"
inkscape:flatsided="false"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 12.049596,42.113629 -0.611029,-1.058333 -0.611029,-1.058333 1.222058,0 1.222058,0 -0.611029,1.058333 z"
inkscape:transform-center-y="0.3527758" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24"
height="31.999998"
viewBox="0 0 6.3500001 8.4666662"
version="1.1"
id="svg8"
inkscape:version="0.92.2 2405546, 2018-03-11"
sodipodi:docname="select-arrow-intensive.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="11.2"
inkscape:cx="27.151934"
inkscape:cy="16.615415"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-width="1920"
inkscape:window-height="1020"
inkscape:window-x="0"
inkscape:window-y="30"
inkscape:window-maximized="1"
units="px" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-8.8745959,-36.821965)">
<path
sodipodi:type="star"
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2.64583325;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:75.59055328;stroke-opacity:1;paint-order:normal"
id="path4749"
sodipodi:sides="3"
sodipodi:cx="12.049596"
sodipodi:cy="40.702518"
sodipodi:r1="1.411111"
sodipodi:r2="0.70555568"
sodipodi:arg1="1.5707963"
sodipodi:arg2="2.6179939"
inkscape:flatsided="false"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 12.049596,42.113629 -0.611029,-1.058333 -0.611029,-1.058333 1.222058,0 1.222058,0 -0.611029,1.058333 z"
inkscape:transform-center-y="0.3527758" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24"
height="31.999998"
viewBox="0 0 6.3500001 8.4666662"
version="1.1"
id="svg8"
inkscape:version="0.92.2 2405546, 2018-03-11"
sodipodi:docname="select-arrow-normal.svg">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="11.2"
inkscape:cx="27.151934"
inkscape:cy="16.615415"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-width="1920"
inkscape:window-height="1020"
inkscape:window-x="0"
inkscape:window-y="30"
inkscape:window-maximized="1"
units="px" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-8.8745959,-36.821965)">
<path
sodipodi:type="star"
style="opacity:1;fill:#c3c3c3;fill-opacity:1;stroke:none;stroke-width:2.64583325;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:75.59055328;stroke-opacity:1;paint-order:normal"
id="path4749"
sodipodi:sides="3"
sodipodi:cx="12.049596"
sodipodi:cy="40.702518"
sodipodi:r1="1.411111"
sodipodi:r2="0.70555568"
sodipodi:arg1="1.5707963"
sodipodi:arg2="2.6179939"
inkscape:flatsided="false"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 12.049596,42.113629 -0.611029,-1.058333 -0.611029,-1.058333 1.222058,0 1.222058,0 -0.611029,1.058333 z"
inkscape:transform-center-y="0.3527758" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB