Иконка ресурса

FREE Photog : 1.0.3 1.0.3

Нет прав для скачивания
C#:
#region License (GPL v2)
/*
    DESCRIPTION
    Copyright (c) 2024 RFC1920 <desolationoutpostpve@gmail.com>

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License v2.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

    Optionally you can also view the license at <http://www.gnu.org/licenses/>.
*/
#endregion License Information (GPL v2)
using Oxide.Core;
using Oxide.Core.Libraries.Covalence;
using Oxide.Core.Plugins;
using Oxide.Game.Rust.Cui;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Text;
using System.Globalization;
using System.IO;
using System.Linq;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("Photog", "RFC1920", "1.0.3")]
    [Description("Paste photo from Instant Camera to a PhotoFrame")]
    internal class Photog : RustPlugin
    {
        [PluginReference]
        private readonly Plugin ImageLibrary;
        private bool hasImageLibrary;

        private ConfigData configData;
        private const string permUse = "photog.use";
        private List<ulong> isopen = new();

        private const string PHOTOVIEW = "photog.imageviewer";
        private const string PHOTOEDIT = "photog.imageedit";
        private const string PHOTODEL = "photog.imagedel";

        private Dictionary<ulong, NetworkableId> frames = new();
        private Dictionary<ulong, List<UserPhotos>> photos = new();

        #region Message
        private string Lang(string key, string id = null, params object[] args) => string.Format(lang.GetMessage(key, this, id), args);
        private void Message(IPlayer player, string key, params object[] args) => player.Message(Lang(key, player.Id, args));
        private void LMessage(IPlayer player, string key, params object[] args) => player.Reply(Lang(key, player.Id, args));

        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["notauthorized"] = "You don't have permission to use this command.",
                ["imageviewer"] = "Photog Image Viewer",
                ["atlimit"] = "You are at your limit for saved images.  Use the /mfv command to manage them.",
                ["newimage"] = "New image saved as {0}",
                ["usure"] = "Are you sure?",
                ["yes"] = "Yes",
                ["no"] = "No",
                ["cancel"] = "Cancel",
                ["bytes"] = "bytes",
                ["delete"] = "Delete",
                ["pname"] = "Name",
                ["category"] = "Category",
                ["marked"] = "Frame has been marked",
                ["unmarked"] = "Frame has been unmarked"

            }, this);
        }
        #endregion

        private class UserPhotos
        {
            public int id;
            public ulong ownerid;
            public ulong photoID;
            public string imageID;
            public string name;
            public string category;
            public string date;
            public string location;
            public string thumbnail;
        }

        private void DoLog(string message)
        {
            if (configData.debug)
            {
                Interface.GetMod().LogInfo($"{Name}: {message}");
            }
        }

        private void OnServerInitialized()
        {
            permission.RegisterPermission(permUse, this);
            AddCovalenceCommand("mf", "cmdMarkFrame");
            AddCovalenceCommand("mfv", "cmdImageManager");
            LoadConfigVariables();
            LoadData();

            if (ImageLibrary != null) hasImageLibrary = true;
        }

        private void Unload()
        {
            foreach (BasePlayer player in BasePlayer.activePlayerList)
            {
                CuiHelper.DestroyUi(player, PHOTOVIEW);
                CuiHelper.DestroyUi(player, PHOTOEDIT);
                CuiHelper.DestroyUi(player, PHOTODEL);
            }
        }

        private void OnPhotoCaptured(PhotoEntity photo, Item item, BasePlayer player, byte[] numArray)
        {
            // Item is the camera.
            DoLog($"{player.displayName} took a photo {item.name}");
            if (configData.overlayPhotographerName || configData.overlayTimestamp || configData.overlayPosition)
            {
                string text = null;

                if (configData.overlayPhotographerName) text += $"{player.displayName} ";
                if (configData.overlayTimestamp) text += DateTime.Now.ToString("dd MMM yyyy");
                if (configData.overlayPosition) text += $" @ {player.transform.position}";

                numArray = OverlayText(numArray, text);
            }
            if (configData.StoreAndRetrieveUserPhotos)
            {
                SaveImage(player, numArray, photo.net.ID.Value, "png");
            }
            if (frames.ContainsKey(player.userID))
            {
                DoLog("Try to copy photo");
                photo.SetImageData(player.userID, numArray); // This overwrites the photoItem in inventory
                BaseNetworkable frame = BaseNetworkable.serverEntities.Find(frames[player.userID]);
                if (frame is PhotoFrame photoFrame)
                {
                    DoLog("Found a photo frame");
                    BaseNetworkable.LoadInfo info = new()
                    {
                        msg = new ProtoBuf.Entity()
                        {
                            photoFrame = new ProtoBuf.PhotoFrame()
                            {
                                ShouldPool = true,
                                overlayImageCrc = photo.ImageCrc,
                                photoEntityId = photo.net.ID,
                                editHistory = photo.EditingHistory
                            }
                        }
                    };
                    DoLog("Loading image to photo frame");
                    photoFrame.Load(info);
                    photoFrame.SendNetworkUpdateImmediate();
                    if (configData.lockOnPaint)
                    {
                        photoFrame.SetFlagLocal(BaseEntity.Flags.Locked, true);
                    }
                }
                if (!configData.leaveOpen)
                {
                    frames.Remove(player.userID);
                }
                if (configData.destroyPhotoItemAfterPaste)
                {
                    // Not working?
                    BaseNetworkable photoItem = BaseNetworkable.serverEntities.Find(photo.net.ID);
                    photoItem.DestroyShared();
                    player.inventory.containerMain.MarkDirty();
                }
            }
        }

        [Command("mf")]
        private void cmdMarkFrame(IPlayer iplayer, string command, string[] args)
        {
            if (!configData.RequirePermission || (configData.RequirePermission && permission.UserHasPermission(iplayer?.Id, permUse)))
            {
                BasePlayer player = iplayer.Object as BasePlayer;
                PhotoFrame frame = FindEntity(player) as PhotoFrame;
                if (frame != null)
                {
                    if (frames.ContainsKey(player.userID))
                    {
                        frames.Remove(player.userID);
                        Message(iplayer, "unmarked");
                        return;
                    }
                    frames.Add(player.userID, frame.NetworkID);
                    Message(iplayer, "marked");
                }
            }
        }

        [Command("mfv")]
        private void cmdImageManager(IPlayer iplayer, string command, string[] args)
        {
            if (configData.RequirePermission && (!configData.RequirePermission || !permission.UserHasPermission(iplayer?.Id, permUse)))
            {
                Message(iplayer, "notauthorized");
                return;
            }

            if (configData.debug)
            {
                string debug = string.Join(",", args); Puts($"{debug}");
            }

            BasePlayer player = iplayer.Object as BasePlayer;
            if (!photos.ContainsKey(player.userID))
            {
                photos.Add(player.userID, new List<UserPhotos>());
            }

            if (args.Length > 0)
            {
                switch (args[0])
                {
                    case "list":
                        string output = string.Empty;
                        int i = 0;
                        foreach (UserPhotos photo in photos[player.userID])
                        {
                            i++;
                            FileInfo fi = new(photo.location);
                            output += $"{i}: {photo.name} {fi.CreationTime}\n\t{fi.Length} " + Lang("bytes") + "\n";
                        }
                        iplayer.Reply(output);
                        break;
                    case "close":
                        CuiHelper.DestroyUi(player, PHOTOVIEW);
                        CuiHelper.DestroyUi(player, PHOTOEDIT);
                        CuiHelper.DestroyUi(player, PHOTODEL);
                        break;
                    case "edit":
                        {
                            ulong photoid = ulong.Parse(args[1]);
                            if (args.Length == 2)
                            {
                                // Open edit gui
                                ImageEdit(player, photoid);
                            }
                        }
                        break;
                    case "cancel":
                        CuiHelper.DestroyUi(player, PHOTODEL);
                        break;
                    case "delete":
                        {
                            if (args.Length > 1)
                            {
                                ulong photoid = ulong.Parse(args[1]);
                                if (args.Length > 2 && args[2] == "YES")
                                {
                                    UserPhotos up = photos[player.userID].First(s => s.photoID == photoid);
                                    if (up != null)
                                    {
                                        //string dir = Interface.GetMod().DataDirectory + $"/{Name}/{player.displayName}";
                                        if (File.Exists(up.location))
                                        {
                                            File.Delete(up.location);
                                        }
                                        if (File.Exists(up.thumbnail))
                                        {
                                            File.Delete(up.thumbnail);
                                        }
                                        photos[player.userID].Remove(up);
                                        SaveData();
                                        ImageViewer(player);
                                    }
                                    CuiHelper.DestroyUi(player, PHOTODEL);
                                    return;
                                }
                                ImageDelete(player, photoid);
                            }
                        }
                        break;
                }
                return;
            }
            ImageViewer(player);
        }

        private void IsOpen(ulong uid, bool set = false)
        {
            if (set)
            {
                DoLog($"Setting isopen for {uid}");

                if (!isopen.Contains(uid)) isopen.Add(uid);
                return;
            }

            DoLog($"Clearing isopen for {uid}");

            isopen.Remove(uid);
        }

        private void ImageDelete(BasePlayer player, ulong photoid)
        {
            IsOpen(player.userID, true);
            CuiHelper.DestroyUi(player, PHOTODEL);

            CuiElementContainer container = UI.Container(PHOTODEL, UI.Color("3b3b3b", 1f), "0.25 0.4", "0.7 0.55", true, "Overlay");
            UI.Label(ref container, PHOTODEL, UI.Color("#ffffff", 1f), Lang("usure"), 12, "0.1 0.92", "0.9 1");
            UI.Button(ref container, PHOTODEL, UI.Color("#55d840", 1f), Lang("yes"), 12, "0.1 0.4", "0.25 0.6", $"mfv delete {photoid} YES ");
            UI.Button(ref container, PHOTODEL, UI.Color("#5540d8", 1f), Lang("no"), 12, "0.27 0.4", "0.47 0.6", "mfv cancel ");

            CuiHelper.AddUi(player, container);
        }

        private void ImageEdit(BasePlayer player, ulong photoid)
        {
            IsOpen(player.userID, true);
            CuiHelper.DestroyUi(player, PHOTOEDIT);
        }

        private void ImageViewer(BasePlayer player)
        {
            DoLog("ImageBViewer");
            IsOpen(player.userID, true);
            CuiHelper.DestroyUi(player, PHOTOVIEW);

            CuiElementContainer container = UI.Container(PHOTOVIEW, UI.Color("2b2b2b", 1f), "0.05 0.05", "0.95 0.95", true, "Overlay");
            UI.Label(ref container, PHOTOVIEW, UI.Color("#ffffff", 1f), Lang("imageviewer"), 24, "0.2 0.92", "0.65 1");
            UI.Button(ref container, PHOTOVIEW, UI.Color("#d85540", 1f), Lang("close"), 12, "0.93 0.95", "0.99 0.98", "mfv close");

            int row = 0, col = 0;
            //string playerdir = Interface.GetMod().DataDirectory + $"/{Name}/{player.displayName}";
            foreach (UserPhotos photo in photos[player.userID])
            {
                row++;
                if (row > 10)
                {
                    row = 1; col += 4;
                }
                float[] pb = GetButtonPositionI(row, col);
                //string url = $"file://{photo.thumbnail}";
                //string url = photo.thumbnail;
                string imageId = string.Empty;
                if (hasImageLibrary)
                {
                    imageId = (string)ImageLibrary.Call("GetImage", photo.location, 0UL);
                }

                //UI.Image(ref container, PHOTOVIEW, "#000000", url, $"{pb[0]} {pb[1]}", $"{pb[0] + ((pb[2] - pb[0]) / 2)} {pb[3]}");
                UI.Image(ref container, PHOTOVIEW, "#000000", imageId, $"{pb[0]} {pb[1]}", $"{pb[0] + ((pb[2] - pb[0]) / 2)} {pb[3]}");
                pb = GetButtonPosition(row, col + 1);
                UI.Button(ref container, PHOTOVIEW, UI.Color("#55d840", 1f), Lang("edit"), 11, $"{pb[0]} {pb[1]}", $"{pb[0] + ((pb[2] - pb[0]) / 2)} {pb[3]}", $"mfv edit {photo.photoID}");
                pb = GetButtonPosition(row, col + 2);
                UI.Button(ref container, PHOTOVIEW, UI.Color("#55d840", 1f), Lang("delete"), 11, $"{pb[0]} {pb[1]}", $"{pb[0] + ((pb[2] - pb[0]) / 2)} {pb[3]}", $"mfv delete {photo.photoID}");
            }
            CuiHelper.AddUi(player, container);
        }

        private float[] GetButtonPosition(int rowNumber, int columnNumber, float colspan = 1f)
        {
            float offsetX = 0.05f + (0.126f * columnNumber);
            float offsetY = (0.87f - (rowNumber * 0.164f));

            Puts($"GetButtonPosition: {offsetX}, {offsetY}, {offsetX + (0.226f * colspan)}, {offsetY + 0.03f}");
            return new float[] { offsetX, offsetY, offsetX + (0.226f * colspan), offsetY + 0.03f };
        }

        private float[] GetButtonPositionI(int rowNumber, int columnNumber)
        {
            float offsetX = 0.05f + (0.126f * columnNumber);
            float offsetY = 0.87f - (rowNumber * 0.04f);
            float offsetXr = (float)Math.Round((offsetX + 0.08f) * 1000f) / 1000f;
            float offsetYr = (float)Math.Round((offsetY + 0.08f) * 1000f) / 1000f;

            Puts($"GetButtonPositionI: {offsetX}, {offsetY}, {offsetXr}, {offsetYr}");
            return new float[] { offsetX, offsetY, offsetXr, offsetYr };
        }

        private int CountFiles(BasePlayer player)
        {
            try
            {
                string dir = Interface.GetMod().DataDirectory + $"/{Name}/{player.displayName}";
                int count = Directory.GetFiles(dir, "*.*").Count(s => s.EndsWith(".png") || s.EndsWith(".jpg"));
                DoLog($"{player.displayName} has {count} files");
                return count;
            }
            catch { return 0; }
        }

        private void SaveImage(BasePlayer player, byte[] numArray, ulong photoID, string format = "png")
        {
            if (CountFiles(player) >= configData.StoreAndRetrieveLimit)
            {
                Message(player.IPlayer, "atlimit");
                return;
            }
            DoLog($"SaveImage called for {player?.displayName}");
            if (!photos.ContainsKey(player.userID))
            {
                photos.Add(player.userID, new List<UserPhotos>());
            }
            string dir = Interface.GetMod().DataDirectory + $"/{Name}/{player.displayName}";
            string filename = DateTime.Now.ToString("ddMMyy_hhmmss") + $".{format}";

            Bitmap bmp;
            using (MemoryStream m = new(numArray))
            {
                bmp = new Bitmap(m);
            }

            // Create player data folder
            Core.Configuration.DynamicConfigFile x = Interface.Oxide.DataFileSystem.GetFile($"{dir}/info");
            x.WriteObject(player.displayName);

            DoLog($"Saving image as {dir}/{filename}");
            switch (format)
            {
                case "jpg":
                case "jpeg":
                    DoLog("Saving as JPEG");
                    bmp.Save($"{dir}/{filename}", System.Drawing.Imaging.ImageFormat.Jpeg);
                    break;
                default:
                    DoLog("Saving as PNG");
                    bmp.Save($"{dir}/{filename}", System.Drawing.Imaging.ImageFormat.Png);
                    break;
            }
            Message(player.IPlayer, "newimage", filename);

            DoLog("Saving thumbnail image");
            Image image = Image.FromFile($"{dir}/{filename}");
            Image thumb = image.GetThumbnailImage(120, 80, () => false, IntPtr.Zero);
            string thumbpath = Path.ChangeExtension($"{dir}/{filename}", "thumb.png");
            thumb.Save(thumbpath);

            DoLog("Adding image record");
            string imageId = string.Empty;
            if (hasImageLibrary)
            {
                ImageLibrary.Call("AddImage", $"{dir}/{filename}", filename, 0UL);
                string imgid = (string)ImageLibrary.Call("GetImage", filename);
            }
            photos[player.userID].Add(new UserPhotos()
            {
                id = UnityEngine.Random.Range(1, 99999999),
                date = DateTime.Now.ToString(),
                location = $"{dir}/{filename}",
                thumbnail = thumbpath,
                name = filename,
                ownerid = player.userID,
                photoID = photoID,
                imageID = imageId
            });
            SaveData();
        }

        private byte[] OverlayText(byte[] numArray, string text)
        {
            // https://stackoverflow.com/questions/40041373/add-text-to-image-without-saving
            DoLog("OverlayText");
            Bitmap bmp;

            using (MemoryStream m = new(numArray))
            {
                bmp = new Bitmap(m);
            }
            DoLog($"IMG w{bmp.Width}/h{bmp.Height}");
            // Always 854x480
            int sLeft = bmp.Width / 6;// * 4;
            int sHeight = bmp.Height / 5 * 4;
            DoLog($"Overlay text '{text}' at {sHeight}/{sLeft} h 50 w 650");
            RectangleF rectf = new(sLeft, sHeight, 650, 50);
            int fontsize = configData.overlayFontSize > 10 ? configData.overlayFontSize : 10;
            using (System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(bmp))
            {
                Brush brush = configData.blackTextInsteadOfWhite ? Brushes.Black : Brushes.White;
                using (System.Drawing.Font arialFont = new("Arial", fontsize))
                {
                    g.SmoothingMode = SmoothingMode.AntiAlias;
                    g.InterpolationMode = InterpolationMode.HighQualityBicubic;
                    g.PixelOffsetMode = PixelOffsetMode.HighQuality;
                    g.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
                    g.DrawString(text, new System.Drawing.Font("courier sans", fontsize, System.Drawing.FontStyle.Bold), brush, rectf);
                }
            }
            MemoryStream ms = new();
            bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
            byte[] arr = new byte[ms.Length];

            ms.Position = 0;
            ms.Read(arr, 0, (int)ms.Length);
            ms.Close();

            return arr;
        }

        private static BaseEntity FindEntity(BasePlayer player)
        {
            RaycastHit hit;
            if (!Physics.Raycast(player.eyes.HeadRay(), out hit)) return null;
            return hit.GetEntity();
        }

        public class ConfigData
        {
            public bool lockOnPaint;
            public bool leaveOpen;
            public bool overlayPhotographerName;
            public bool overlayTimestamp;
            public bool overlayPosition;
            public int overlayFontSize;
            public bool blackTextInsteadOfWhite;
            public bool destroyPhotoItemAfterPaste;
            public bool debug;
            public bool RequirePermission;
            public bool StoreAndRetrieveUserPhotos;
            public int StoreAndRetrieveLimit;
            public VersionNumber Version;
        }

        private void LoadConfigVariables()
        {
            configData = Config.ReadObject<ConfigData>();

            if (configData.Version < new VersionNumber(1, 0, 3))
            {
                configData.StoreAndRetrieveLimit = 20;
            }
            configData.Version = Version;
            SaveConfig(configData);
        }

        private void SaveConfig(ConfigData config)
        {
            Config.WriteObject(config, true);
        }

        protected override void LoadDefaultConfig()
        {
            DoLog("Creating new config file.");
            ConfigData config = new()
            {
                lockOnPaint = false,
                leaveOpen = false,
                overlayPhotographerName = true,
                overlayFontSize = 12,
                debug = false,
                RequirePermission = false,
                StoreAndRetrieveLimit = 20
            };
        }

        private void LoadData()
        {
            photos = Interface.GetMod().DataFileSystem.ReadObject<Dictionary<ulong, List<UserPhotos>>>(Name + "/userphotos");

            bool badid = false;
            foreach (KeyValuePair<ulong, List<UserPhotos>> userdata in photos)
            {
                foreach (UserPhotos photo in userdata.Value)
                {
                    if (photo.id == 0)
                    {
                        badid = true;
                        photo.id = UnityEngine.Random.Range(1, 99999999);
                    }
                }
            }
            if (badid) { SaveData(); }
        }

        private void SaveData()
        {
            Interface.GetMod().DataFileSystem.WriteObject(Name + "/userphotos", photos);
        }

        public static class UI
        {
            public static CuiElementContainer Container(string panel, string color, string min, string max, bool useCursor = false, string parent = "Overlay")
            {
                return new CuiElementContainer()
                {
                    {
                        new CuiPanel
                        {
                            Image = { Color = color },
                            RectTransform = {AnchorMin = min, AnchorMax = max},
                            CursorEnabled = useCursor
                        },
                        new CuiElement().Parent = parent,
                        panel
                    }
                };
            }

            public static void Panel(ref CuiElementContainer container, string panel, string color, string min, string max, bool cursor = false)
            {
                container.Add(new CuiPanel
                {
                    Image = { Color = color },
                    RectTransform = { AnchorMin = min, AnchorMax = max },
                    CursorEnabled = cursor
                },
                panel);
            }

            public static void Label(ref CuiElementContainer container, string panel, string color, string text, int size, string min, string max, TextAnchor align = TextAnchor.MiddleCenter)
            {
                container.Add(new CuiLabel
                {
                    Text = { Color = color, FontSize = size, Align = align, Text = text },
                    RectTransform = { AnchorMin = min, AnchorMax = max }
                },
                panel);
            }

            public static void Button(ref CuiElementContainer container, string panel, string color, string text, int size, string min, string max, string command, TextAnchor align = TextAnchor.MiddleCenter)
            {
                container.Add(new CuiButton
                {
                    Button = { Color = color, Command = command, FadeIn = 0f },
                    RectTransform = { AnchorMin = min, AnchorMax = max },
                    Text = { Text = text, FontSize = size, Align = align }
                },
                panel);
            }

            public static void Input(ref CuiElementContainer container, string panel, string color, string text, int size, string min, string max, string command, TextAnchor align = TextAnchor.MiddleCenter)
            {
                container.Add(new CuiElement
                {
                    Name = CuiHelper.GetGuid(),
                    Parent = panel,
                    Components =
                    {
                        new CuiInputFieldComponent
                        {
                            Align = align,
                            CharsLimit = 30,
                            Color = color,
                            Command = command + text,
                            FontSize = size,
                            IsPassword = false,
                            Text = text,
                            NeedsKeyboard = true
                        },
                        new CuiRectTransformComponent { AnchorMin = min, AnchorMax = max },
                        new CuiNeedsCursorComponent()
                    }
                });
            }

            public static void Icon(ref CuiElementContainer container, string panel, string color, string imageurl, string min, string max)
            {
                container.Add(new CuiElement
                {
                    Name = CuiHelper.GetGuid(),
                    Parent = panel,
                    Components =
                    {
                        new CuiRawImageComponent
                        {
                            Url = imageurl,
                            //Png = imageurl,
                            //Sprite = imageurl,
                            Sprite = "assets/content/textures/generic/fulltransparent.tga",
                            Color = color
                        },
                        new CuiRectTransformComponent
                        {
                            AnchorMin = min,
                            AnchorMax = max
                        }
                    }
                });
            }

            public static void Image(ref CuiElementContainer container, string panel, string color, string imageurl, string min, string max)
            {
                container.Add(new CuiElement
                {
                    Name = CuiHelper.GetGuid(),
                    Parent = panel,
                    Components =
                    {
                        new CuiRawImageComponent
                        {
                            Png = imageurl,
                            Sprite = "assets/content/textures/generic/fulltransparent.tga",
                            Color = color
                        },
                        new CuiRectTransformComponent
                        {
                            AnchorMin = min,
                            AnchorMax = max
                        }
                    }
                });
            }

            public static string Color(string hexColor, float alpha)
            {
                if (hexColor.StartsWith("#"))
                {
                    hexColor = hexColor.Substring(1);
                }
                int red = int.Parse(hexColor.Substring(0, 2), NumberStyles.AllowHexSpecifier);
                int green = int.Parse(hexColor.Substring(2, 2), NumberStyles.AllowHexSpecifier);
                int blue = int.Parse(hexColor.Substring(4, 2), NumberStyles.AllowHexSpecifier);
                return $"{(double)red / 255} {(double)green / 255} {(double)blue / 255} {alpha}";
            }
        }
    }
}

Автор
15FPS
Скачиваний
0
Просмотры
1
Первый выпуск
Обновление
Оценка
0.00 звёзд 0 оценок
Сверху