• Как реализовать идеальный метод indexOf?

    trapwalker
    @trapwalker
    Программист, энтузиаст
    Вы неверно понимаете суть О-нотации. Почитайте книги Дональда Кнута про это.
    O(3) - это то же самое, что O(1). Нет разницы. O(N), O(N+1000), O(10*N) - это тоже одно и то же.
    В таких случаях речь всегда идёт не про конкретный кейс, а про обобщенный. Вы не знаете в каком порядке элементы вашего массива, где находится искомый, сколько всего элементов будет в конкретных кейсах, поэтому определяется ряд случаев: средний (по вероятности, если входные данные рандомные), худший (чтобы понимать границы и сколько может "висеть" алгоритм теоретически). Лучшие варианты обычно никого не интересуют, потому что и вероятность их мала, и смысла никакого нет в столь малых величинах.

    У вас типичный случай компромисса в реализации структуры данных. Вы всегда балансируете между памятью и скоростью. Больших семь шапок из овцы не выкроить никак.
    То есть, вы можете сделать такую структуру данных, которая "под капотом" будет держать древовидный индекс с данными или отсортированную по ключу карту значений для бинарного поиска. Хотя эти варианты - суть одно и то же.
    Если не рассматривается вариант размена производительности на память, то в этой задаче у вас будет только O(N) без вариантов.
    Если усложнить структуру данных, то можно добиться и O(logN) при поиске, и даже O(1). Почитайте как устроен словарь в питоне.

    Да, помимо сложности поиска у вас будет сложность вставки в структуру новых элементов. И тут опять трейд-офф. Ну а что вы хотели?
    Ответ написан
    2 комментария
  • Почему отказались от оператора GoTo в высокоуровневых языках?

    wataru
    @wataru
    Разработчик на С++, экс-олимпиадник.
    Поскольку goto позволяет писать очень запутанный и непонятный код. Слишком легко написать плохой код.
    Ну не могут люди в голове удерживать спагетти из кучи возможных путей исполнения. if/else, да циклы - это людям понятно. Появилась инструкция goto изначально, потому что в машинных кодах, а значит и в асемблере эта инструкция есть. Оттуда она перекочевала в языки более высокого уровня. Процессоры, наоборот, if/else вообще не умеют. Вместо это там всякие условные goto.
    В новых языках программирования goto вообще нет, а в старых его обычно запрещают использовать в правилах кодовой базы. Если кто злоупотребляет - ему дают по рукам.
    Ответ написан
  • Почему отказались от оператора GoTo в высокоуровневых языках?

    saboteur_kiev
    @saboteur_kiev
    software engineer
    Никакие комьютеры не отказались от goto.
    Кроме того, это не функция, а оператор.

    Но если писать длинный код активно используя goto, в нем будет крайне сложно разобраться.
    Поэтому в некоторых языках программирования его может и не быть. Но на архитектурном уровне и низкоуровневом ассемблере, он естественно есть.
    Ответ написан
    Комментировать
  • В чем проблема плагинов "раскрашивания" скобок для IDE?

    AshBlade
    @AshBlade
    Просто хочу быть счастливым
    Слишком много разноцветных побрекушек только отвлекают.
    А если тебе нужно раскрашивать скобочки, чтобы понимать где ты находишься, то их раскрашивание это только маскирование симптомов - проблема уже в архитектуре кода
    Ответ написан
    4 комментария
  • Как отменять коммит слияния, чтобы потом без проблем выполнить повторное слияние?

    sergey-kuznetsov
    @sergey-kuznetsov Куратор тега Git
    Автоматизатор
    Линус Торвальдс уже подробно комментировал подобную ситуацию.

    И вы совершенно правильно всё поняли. Реверс коммита слияния восстанавливает код, который был изменён этим коммитом, но ничего не делает с эффектами, которые это слияние оказало на историю. Слияние всё ещё будет существовать, и оно всё ещё будет восприниматься как объединение двух веток, и будущие слияния будут воспринимать это слияние как последнее общее состояние — и реверт никак на это не повлияет.

    Таким образом, «revert» отменяет изменения кода, но это совсем не «undo» в том смысле, что он не отменяет влияния коммита слияния на историю репозитория.

    Если дефектная ветвь задачи была доработана путем добавления новых коммитов, то сделать реверт предыдущего реверта перед повторным слиянием будет правильным решением. Но лучше сделать повторный реверт не в самой целевой ветке, а создав от неё другую временную ветку, в ней выполнить реверт реверта и затем слить эту временную ветку с веткой задачи. Тогда повторное слияние ветки задачи в ваш проект пройдет как изначально задумывалось.

    Но есть ещё один способ — можно по-настоящему отменить слияние, пересобрав тематическую ветку заново. Тогда не придется делать повторный revert.

    Давайте вернемся к вашей ситуации:
     P---o---o---M---x---x---W---x
      \         /
       A---B---C----------------D---E   <-- доработанная ветка задачи

    Где M — это слияние, которое вносит эти преждевременные изменения в основную ветку, x — это изменения не связанные с тем, что сделала тематическая ветка и уже внесённые в основную, а W - это «реверт M» (разве W не выглядит как перевернутое M?)

    Мы можем пересобрать тематическую ветку заново

    $ git rebase --no-ff P

    Получим:
       A'---B'---C'------------D'---E'  <-- пересозданная тематическая ветка
      /
     P---o---o---M---x---x---W---x
      \         /
       A---B---C----------------D---E

    Теперь можно слить пересозданную ветку с основной, не откатывая коммит W, и история основной ветки будет выглядеть так:
       A'---B'---C'------------D'---E'
      /                              \
     P---o---o---M---x---x---W---x---M2
      \         /
       A---B---C

    Правда такой трюк не сработает, если вы успели до слияния пообновлять тематическую ветку из основной. Эти коммиты слияния намертво свяжут две цепочки коммитов и не дадут сделать rebase.

    Но если обсуждать именно грамотный способ работы, то не стоит возвращаться в тематическую ветку после того, как она уже слита с основной. Ветка задачи всегда удаляется сразу после слияния, а все доработки и исправления делаются уже в новой ветке, как правильно заметил Дмитрий Гординский в соседнем ответе.
    Ответ написан
    Комментировать
  • Почему не могу закоммитить?

    sergey-kuznetsov
    @sergey-kuznetsov Куратор тега Git
    Автоматизатор
    первый раз работаю с git

    В первую очередь вам надо почитать учебник. Git это не та программа, с которой вы сходу можете начать работать. Он конечно простой, но вообще не интуитивно понятный.

    выдает 10000 файлов в каталоге

    Причина проста — вы инициализировали репозиторий находясь в корне вашего домашнего каталога. Нельзя так делать, если не хотите потерять свои файлы, бездумно вбивая команды типа stash.

    Что мне делать

    Удалить (или переименовать) подкаталог .git в котором хранится репозиторий. Затем создать его в правильном месте — в каталоге вашего проекта.
    Ответ написан
    Комментировать
  • Как спрятать файлы через gitignore, чтобы заходя в мой репозиторий не видели файлы проекта но могли открыть проект (сайт) по ссылке?

    vhood
    @vhood
    Не забывайте отмечать решения
    Поскольку файлы уже отслеживались и есть в git, недостаточно добавить их в .gitignore, помимо этого нужно удалить их из git

    git rm --cached index.html

    Если сайт хостится через GitHub Pages, то как раз index.html из репозитория и считывается для этого. Если удалить index.html, никакой сайт никто не увидит. Хотите сделать сайт с закрытым кодом - храните код в приватном репозитории и показывайте через платный хостинг.
    Ответ написан
    3 комментария
  • Как скрестить ElasticSearch и MySQL?

    mayton2019
    @mayton2019
    Bigdata Engineer
    В этом мало смысла потому как назначение Эластика - это делать быстрый поиск в не-структурированных
    (non-structured) данных таких как логи, дампы сетевого трафика или просто месседжи или текст.

    Назначение MySQL (процентов 99) это хранение реляционных данных. Тоесть данных где есть нормализация
    (1-2-3 НФ).

    И где... в какой части вы пересекаетесь - непонятно. Либо неправильно используется MySQL либо не там Эластик.
    Ответ написан
    2 комментария
  • Как подружить html с json?

    vhood
    @vhood
    Не забывайте отмечать решения
    HTML - язык разметки, никакие значения он не меняет
    Ответ написан
    Комментировать
  • Как масштабировать число с идеальной точностью?

    vaut
    @vaut
    Предлагаю решить уравнение
    11*x=13
    И почитать что такое рациональные числа.
    Ответ написан
    2 комментария
  • Почему запрос возвращает пустоту?

    ipatiev
    @ipatiev Куратор тега PHP
    Потомок старинного рода Ипатьевых-Колотитьевых
    Если запрос выполняется без ошибок но не возвращает ни одной строки, это значит что в БД отсутствуют данные, подходящие под указанные в запросе условия.
    Ответ написан
    Комментировать
  • Как разделять относительно одинаковые скрипты между клиентами?

    VoidVolker
    @VoidVolker
    Dark side eye. А у нас печеньки! А у вас?
    Декомпозиция и разбиение скриптов на более простые части, а так же добавление гибкости как в плане настроек, так и функционала.
    Ответ написан
    Комментировать
  • Как можно решить проблему с доступам к методам PHP классов унаследованным от одного класса?

    Красиво сделать не получится. Но можно сделать так, чтобы хотя бы на тестах оно упало и показало, что нельзя так делать.

    <?php
    
    class Node
    {
        protected function func1() {
            print "NODE PARENT; ";
        }
    }
    class NodeA extends Node
    {
        public function FUNC2() {
            print "NODE A; ";
        }
    
        // Используем метод родителя внутри этого класса
        public function func1Overrided() {
            print "From parent: " . parent::func1();
        }
    
        // Переопределяем метод так, чтобы его нельзя было использовать
        protected function func1() {
            throw new \Exception("Нельзя вызывать этот метод из NodeB");
        }
    }
    
    class NodeB extends Node
    {
        // Функция инициализации
        public function onInit(NodeA $a): void
        {
            // Сделал так, чтобы не мокать api )
            (function (?NodeA $a) {
                $a->FUNC2(); // Метод успешно вызывется так как он public
                $a->func1();  // Метод теперь кидает исключение, использовать не получится
            })($a);
        }
    }
    
    $nodeA = new NodeA;
    $nodeA->func1Overrided(); // Работает вызов метода funс1 из родителя
    
    $nodeB = new NodeB;
    $nodeB->onInit($nodeA); // Выдаёт ошибку, нельзя использовать метод func1 из класса NodeB


    Но у меня для вас совет: старайтесь как можно меньше использовать наследование. Из моего опыта могу сказать, что всякий раз, когда я с помощью наследования пытаюсь решить хоть сколько-нибудь сложную задачу, постоянно вылазят такие подводные камни, что жить не хочется. Используйте композицию и интерфейсы, и вам не придётся заниматься подобной ерундой.

    Вот минимально-инвазивное решение, которое позволит и наследование сохранить (если оно прям ну вот сильно надо), и решить проблему при помощи композиции, основанной на трейте.

    <?php
    // Делаем общий трейт для всех классов
    trait Func1 {
        private function func1() {
            print "FUNC1; ";
        }
    }
    
    class Node
    {
        // Включаем трейт
        use Func1;
    }
    
    class NodeA extends Node
    {
        // Включаем трейт
        use Func1;
    
        public function FUNC2() {
            print "FUNC 2 NODEA; ";
        }
    }
    
    class NodeB extends Node
    {
        // Включаем трейт
        use Func1;
    
        // Функция инициализации
        public function onInit(NodeA $a): void
        {
            // Добавить зависимость
            (function (?NodeA $a) {
                $a->FUNC2(); // Метод успешно вызывется так как он public
                $a->func1();  // Метод использовать не получится, т.к. он private
            })($a);
        }
    }
    
    $nodeA = new NodeA;
    
    $nodeB = new NodeB;
    $nodeB->onInit($nodeA); // Выдаёт ошибку, нельзя использовать метод func1 из класса NodeB
    Ответ написан
  • Где лучше всего презентовать свою CMS?

    dima9595
    @dima9595
    Junior PHP
    Куда ее можно выложить, чтобы принести пользу миру?

    Гитхаб и т.п. площадки. Сделайте из своего проекта опенсорс.
    Ответ написан
  • Как сделать выбор из MYSQL с LIMIT и JOIN без среза данные?

    Rsa97
    @Rsa97
    Для правильного вопроса надо знать половину ответа
    SELECT *
      FROM (
        SELECT *
          FROM firm
          LIMIT 10
          OFFSET 0
      ) AS f
      LEFT JOIN staff AS s ON s.fid = f.id
    Ответ написан
    1 комментарий
  • Как настроить Git?

    это командная строка, через неё можешь пользоваться гитом, и не только
    Ответ написан
    Комментировать
  • Персональный гугл, если ли?

    Daemon23RUS
    @Daemon23RUS
    Краулер, который ходит по интернетам и ищет сам, то о чём я его попросил

    Боюсь, что ответ Вашего частного поисковика придется долго ждать. Обратимся к цифрам: сейчас порядка 2 млрд. сайтов ( обратите внимание на то что это сайты, а не страницы, коих на многих сайтах десятки, сотни, тысячи а на части на порядки больше) предположим, что ваш краулер пожирает сайты по 10 шт в секунду (со всеми страницами), так вот ждать ответа придется лет 7.
    И это мы опустили трафик, который сожрет краулер. А там все за гранью не то что домашнего сервера, а не все страны потребляют такой объем.
    P.S. Тут вопрос не в алгоритмах или коде, вопрос в объеме "перевариваемой" информации
    Ответ написан
    3 комментария
  • Персональный гугл, если ли?

    sergey-gornostaev
    @sergey-gornostaev
    Седой и строгий
    Google - это прежде всего инфраструктура, а не софт. Как только сможете позволить себе потратить десятки миллиардов долларов на постройку ЦОДов, можно и персональный Google будет завести.
    Ответ написан
    3 комментария
  • Как правильно разрабатывать symfony приложение локально?

    Я использую для локальной разработки docker compose.
    В его конфигурационном файле собираю все нужные контейнеры (PHP, MySQL, Rabbit, Mongo и т.д.) в сеть. Для PHP делаю два контейнера из разных образов:
    Один из образа php-fpm, это сам сервер.
    Второй из образа php-cli, он используется как раз для всех консольных команд. Именню в него устанавливаю Composer.
    Для того, чтобы не писать для запуска консольных команд постоянно это заклинание "docker compose run ...", я делаю Makefile, в котором прописываю часто-используемые команды.
    И, набрав "make cc" на самом деле запускаю "docker-compose run --rm php-cli bin/console c:c"
    Чтобы это всё работало без необходимости постоянно перезапускать docker-compose, я файлы проекта пробрасываю внутрь контейнеров не копированием, а через вольюм, и когда я буду их менять, они будут меняться и внутри контейнера. Так же подключаю прямо внутрь контейнера .env файл, чтобы все переменные окружения туда пробросились.
    Я вам прямо закину кучу файлов из одного из своих старых проектов, может накопаете там идей каких-то для себя:
    1. docker-compose.yml
    version: '3.8'
    services:
      # Поднимаю сразу реверс-прокси, чтобы пробросить всё наружу на нужный мне порт.m
      nginx:
        build:
          context: ./.docker/nginx
          dockerfile: Dockerfile.dev
        volumes:
          - ./:/app
        depends_on:
          - php-fpm
        ports:
          - "8080:80"
    
      php-fpm:
        build:
          context: ./.docker/php
          dockerfile: Dockerfile.fpm-dev
          args:
            IMAGE_ENV: dev
        volumes:
          # Вот тут вся магия) Мы просто подключаем файлы проекта в виде вольюма
          # Т.е. имеем доступ ко всем файлам и изнутри и снаружи контейнера. 
          # Внутри контейнера файлы будут видны по пути /app
          - ./:/app
        environment:
          # Даже XDEBUG можно пробросить для IDE
          XDEBUG_CONFIG: 'discover_client_host=true client_host=127.0.0.1 log_level=0 client_port=9003'
          PHP_IDE_CONFIG: "serverName=Docker"
        env_file:
          - .env.local
        depends_on:
          - mysql
    
      # Делаю отдельный контейнер для комманд-лайн утилит, 
      # чтобы не проще запускать консоль Symfony. Мне так удобнее.
      # Здесь будет всё практически как и у php-fpm, только без самого php-fpm процесса
      php-cli:
        build:
          context: ./.docker/php
          dockerfile: Dockerfile.cli-dev
          args:
            IMAGE_ENV: dev
        volumes:
          - ./:/app
          - composer:/root/.composer/cache
        environment:
          XDEBUG_CONFIG: 'discover_client_host=true client_host=127.0.0.1 log_level=0 client_port=9003'
          PHP_IDE_CONFIG: "serverName=Docker"
        env_file:
          - .env.local
        depends_on:
          - mysql
    
      mysql:
        build:
          context: ./.docker/db
          dockerfile: Dockerfile
        volumes:
          - mysql:/var/lib/mysql
        env_file:
          - .env.local
        environment:
          MYSQL_ROOT_HOST: '%'  # needs to be enclosed with quotes
        ports:
          - "6603:3306"
    
    # Тут ещё два дополнительных вольюма. Один для кэша composer, а второй для файлов базы данных, чтобы она не убивалась при перезапуске наших контейнеров. Убивать мы её будем только сознательно. если нам надо её очистить.
    volumes:
      composer:
      mysql:


    2. Все три докерфайла
    Файлы с конфигурациями, которые будут копироваться внутри докерфайлов, кидать не буду, у вас всё своё будет в любом случае.

    Dockerfile.dev - для nginx.
    # Образ Nginx
    FROM nginx:alpine
    
    # Копируем файл с настройками
    COPY ./task/default.conf /etc/nginx/conf.d/default.conf
    
    # Устанавливаем рабочую директорию
    WORKDIR /app


    Dockerfile.fpm-dev - для сервера php-fpm
    # Образ php-fpm
    FROM php:8.2-fpm
    
    ARG IMAGE_ENV
    
    # Устанавливаем нужные пакеты и расширения
    RUN apt-get update && apt-get install -y -qq libpq-dev unzip libxml2-dev libicu-dev libonig-dev zlib1g-dev libpng-dev librabbitmq-dev \
        && docker-php-ext-configure pdo_mysql --with-pdo-mysql=mysqlnd && docker-php-ext-install pdo_mysql && \
        docker-php-ext-configure intl && docker-php-ext-install intl && \
        pecl install xdebug amqp && rm -rf /tmp/pear && docker-php-ext-enable xdebug amqp && \
        rm -rf /var/lib/apt/lists/*
    
    # Use the default development configuration
    RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini"
    
    # Копируем файл с настройками xdebug
    COPY ./php.d/${IMAGE_ENV}/default.ini /usr/local/etc/php/conf.d/default.ini
    COPY ./php.d/${IMAGE_ENV}/xdebug.ini /usr/local/etc/php/conf.d/xdebug.ini
    
    
    # Создаем пользователя app, чтобы не работать под рутом
    RUN adduser --disabled-password --gecos '' --uid 1000 app
    USER app
    
    # Устанавливаем рабочую директорию
    WORKDIR /app


    Dockerfile.cli-dev - это наш консольный php для запуска консольных команд. Тут практически то же самое, плюс тот самый Composer, ради которого это всё и писалось)
    # Образ php-cli
    FROM php:8.2-cli
    
    ARG IMAGE_ENV
    
    # Устанавливаем нужные пакеты и расширения
    RUN apt-get update && apt-get install -y -qq git unzip libpq-dev libxml2-dev libicu-dev librabbitmq-dev && \
        docker-php-ext-configure pdo_mysql --with-pdo-mysql=mysqlnd && docker-php-ext-install pdo_mysql && \
        docker-php-ext-configure intl && docker-php-ext-install intl && \
        pecl install xdebug amqp && rm -rf /tmp/pear && docker-php-ext-enable xdebug amqp && \
        rm -rf /var/lib/apt/lists/*
    
    # Use the default development configuration
    RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini"
    
    # Копируем файл с настройками xdebug
    COPY ./php.d/${IMAGE_ENV}/default.ini /usr/local/etc/php/conf.d/default.ini
    COPY ./php.d/${IMAGE_ENV}/xdebug.ini /usr/local/etc/php/conf.d/xdebug.ini
    
    # Качаем и устанавливаем composer
    RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/bin --filename=composer --quiet
    
    # Если всё же будете работать в контейнере под рутом, то заставляем composer работать под рутом
    # ENV COMPOSER_ALLOW_SUPERUSER 1
    
    # Создаем пользователя app, чтобы не работать под рутом
    RUN adduser --disabled-password --gecos '' --uid 1000 app
    USER app
    
    # Устанавливаем рабочую директорию
    WORKDIR /app


    3. И далее звезда шоу - Makefile с кучей разных скриптов, облегчающих жизнь.
    make init - чистая сборка проекта, тут можно очищать базу и загружать в нее фикстуры
    make up - просто запуск проекта, без очистки, перед началом обычного рабочего дня.
    make down - остановка проекта
    make cc - очистка кэша Symfony.
    И т.д. и т.п.
    # Если надо подключить shell переменные, то создаём тут же ..env.local файл и делаем.
    include .env.local
    export
    
    # Поддержка возможности запускать шелл-скрипты
    SHELL:=/bin/bash
    
    # Кэш
    c: cache-clear cache-warmup
    cc: cache-clear
    cw: cache-warmup
    cache-clear:
    	docker-compose run --rm php-cli bin/console c:c
    cache-warmup:
    	docker-compose run --rm php-cli bin/console c:w
    
    up: docker-up
    down: docker-down
    restart: docker-down docker-up
    init: docker-down init-common
    reset: docker-down-clear init-common
    init-common: clear docker-pull docker-build docker-up app-init
    test: test
    test-coverage: test-coverage
    test-unit: test-unit
    test-unit-coverage: test-unit-coverage
    
    # Если в контейнере другой пользователь, то будут ошибки доступа при редактировании файлов.
    # Поэтому надо сделать chown и chmod для папки var (кэш и логи)
    chown:
    	sudo chown -R 1000:1000 . # Тут указываем свои данные пользователя с хоста
    	sudo chmod -R 777 ./var/
    
    docker-up:
    	docker-compose up -d
    
    docker-down:
    	docker-compose down --remove-orphans
    
    docker-down-clear:
    	docker-compose down -v --remove-orphans
    
    docker-pull:
    	docker-compose pull
    
    docker-build:
    	docker-compose build
    
    app-init: composer-install app-wait-db app-migrations ready
    
    clear:
    	docker run --rm -v ${PWD}:/app --workdir=/app alpine rm -f .ready
    
    ready:
    	docker run --rm -v ${PWD}:/app --workdir=/app alpine touch .ready
    
    test:
    	docker-compose run --rm php-cli php bin/phpunit
    
    test-coverage:
    	docker-compose run --rm php-cli php bin/phpunit --coverage-clover var/clover.xml --coverage-html var/coverage
    
    test-unit:
    	docker-compose run --rm php-cli php bin/phpunit --testsuite=unit
    
    test-unit-coverage:
    	docker-compose run --rm php-cli php bin/phpunit --testsuite=unit --coverage-clover var/clover.xml --coverage-html var/coverage
    
    composer-install:
    	docker-compose run --rm php-cli composer -V
    	docker-compose run --rm php-cli composer install
    
    app-create-db:
    	docker-compose run --rm php-cli ./bin/console doctrine:database:create
    
    app-wait-db:
    	#docker-compose exec lkui-mysql mysqladmin --user=root --password=password ping --silent --wait=60 &> /dev/null
    	#sleep 20
    	until docker-compose exec -T mysql mysqladmin ping -u root -P ${MYSQL_TCP_PORT} -p${MYSQL_ROOT_PASSWORD} | grep "mysqld is alive" ; do >&2 echo "MySQL is unavailable - waiting for it... " ; sleep 5 ; done
    
    app-migrations:
    	docker-compose run --rm php-cli ./bin/console doctrine:migrations:migrate --no-interaction
    
    app-fixtures:
    	docker-compose run --rm php-cli ./bin/console doctrine:fixtures:load --no-interaction
    
    migration:
    	docker-compose run --rm php-cli ./bin/console make:migration
    
    migrate:
    	docker-compose run --rm php-cli ./bin/console doctrine:migrations:migrate #--no-interaction
    
    fixtures:
    	docker-compose run --rm php-cli ./bin/console doctrine:fixtures:load
    Ответ написан
    2 комментария
  • Как добавить шрифт из безграфического терминала в графический?

    Adamos
    @Adamos
    https://fonts-online.ru/categories/pixel-fonts

    Он прекрасен даже без сглаживания

    Он прекрасен именно, исключительно и только без сглаживания. Как только нужно будет его натянуть на размер, не кратный его пиксельному - красота резко и бесповоротно закончится.
    Ответ написан
    Комментировать