Я обожаю программирование графики

    cover

    Я обожаю программирование графики! Мы все совершаем ошибки в процессе проектирования и написания кода. Иногда это ошибки логики (когда алгоритм продуман неточно или не до конца), иногда ошибки, возникающие по невнимательности, и ещё много-много вариантов. И что происходит в обычном рабочем процессе? — В списках нет необходимых записей, какие-то числа считаются неверно, вываливаются сообщения об ошибках и прочее. В программировании графики всё немного веселее, ведь часто мы получаем результат, который просто не соответствует ожидаемому. В своём небольшом проекте я решил сохранять такие “результаты” на протяжении всего процесса разработки и хотел бы поделиться ими с Вами.

    Всех, кто не любит Android, Live Wallpaper, Minecraft, велосипеды, поток сознания, который слабо привязан к теме и всё около того, хочу сразу предупредить, что их может огорчить содержание этого поста, поэтому продолжайте чтение на свой страх и риск. Оставлю тут также и предупреждение для пользователей мобильного или просто небезлимитного интернета: дальше последует довольно много картинок.

    Здравствуйте!

    Я разработчик ПО с многолетним стажем. Нельзя сказать, что моя работа однообразна и скучна. Я решаю задачи разного уровня, иногда придумываю решения, иногда сам же и реализую их, иногда ищу уязвимости в системах, а иногда и просто рассказываю, почему монитор — это не компьютер. Пожалуй именно то, что я не занят только кодингом, вносит долю разнообразия и творчества в рабочий процесс.

    Бывает также, что из-за различных проблем (бюджет или время на разработку ограничены или просто кого-то из вышестоящих петух клюнул не туда, куда было необходимо), приходится искать готовые решения для многих вещей, что превращает мою работу в детскую игру с кубиками, где требуется “класть кубик на кубик” и смотреть, как манагеры хлопают в ладоши. Пожалуй это и есть та вещь, которая превращает мою работу в рутину (хотя да, даже в моменты “игры с кубиками”, я обычно продолжаю рассказывать, что монитор — это не компьютер). Чтобы хоть как-то избежать рутины и не забыть, каково же реализовывать простые вещи, я люблю в свободное время программировать “всё подряд”. И я заметил, что обожаю именно программирование графики…

    О чём речь?

    Начну с начала, без особых отступлений о том, кто я и почему. Купив новый телефон (тут уже вырисовывается тенденция моих статей на хабре...), мне захотелось поставить себе “живую обоину”, т.к. уж не знаю почему, но нравится мне, когда на экране что-то шевелится. Наверное всё потому, что тогда я чувствую, что купил телефон с четырёхядерным процессором не зря. Погулял по стору, подумал, что бы я хотел видеть на экране и решил, что хочу что-то в стиле майнкрафта, но к сожалению не нашёл ничего, что бы должным образом радовало меня. Тут то я и имел неосторожность решить сделать всё сам…

    Первые сомнения на этот счёт...

    Следует немного отвлечься от основного рассказа и пояснить, почему у меня были сомнения на счёт идеи “а напишу — ка я себе что-то сам”. В школьные годы на компьютерах была установлена игра (как я узнал совсем недавно, игра называлась “Клад” для БК-0010), где белый человечек (за кого и следовало играть) собирал что-то (в моей памяти это были именно ключи, хотя, как выяснилось позже, это должны были быть сундуки), а чёрный человечек за что-то очень ненавидел белого и убивал его прикосновением. Не знаю почему, но мысли об игре вызывали у меня ностальгические чувства, и поэтому я решил “а напишу-ка я её сам”.

    Чтобы не утомлять Вас рассказом о процессе разработки и прочих деталях (не о том моя история), просто опишу смысл: написал, работало именно так, как я запомнил, поиграл один раз, бросил, т.к. уже “наигрался” в процессе отладки.

    Для тех, кому интересно, результат получился вот такой:
    image

    Внутренний голос и тут говорил мне о том, что я не стану пользоваться результатом своих трудов, т.к. слишком насмотрюсь на него за время разработки, но я, как обычно, решил, что “уж в этот раз такого точно не будет” (сила самоубеждения, ага). Забегу вперёд и скажу, что внутренний голос был прав…

    А где тут программирование графики?!

    Попробую вернуться к основному рассказу. Я сразу решил, что не буду использовать ни OpenGL, ни ещё чего-то, что помогло бы мне реализовать задачу — только хардкор. Тем более, меня всегда интересовало взаимодействие Java кода с нативным кодом под Android, а тут ещё и подвернулась неплохая возможность попробовать свои силы в решении этой задачи.
    Сразу решил проверить, смогу ли я вообще реализовать прорисовку с достаточной частотой кадров, с постоянным вызовом нативной библиотеки. Для проверки я реализовал следующую задачу — заполнение экрана картинками с каким-то произвольным коэффициентом “затенения” (по сути просто с параметром яркости, где исходная картинка считается максимально яркой). Написал вариант на Java и C++. Прогнал оба варианта с грубым тестом подсчёта времени и увидел, что в среднем вариант на C++ работал немного быстрее, даже несмотря на то, что сам вывод готового изображения “на экран” всё-же делала Java. В качестве картинок я сразу взял одну из симпатичных, на мой взгляд, текстур для майнкрафта, результат получился примерно таким:
    image

    Поскольку я с самого начала решил, что стану всё реализовывать сам и не буду подглядывать в литературу или искать помощь в интернете, то в края моего изображения выглядели примерно так:
    image

    Очевидно, что неверно обрабатываются изображения, от которых следовало нарисовать только часть, исправляем…
    Итак, задача решаема, а значит можно браться за дело и начинать искать решение и оптимальную реализацию.

    Поскольку для реализации я выбрал решение с использованием JNI, то в результате разработку вёл в смешанном режиме. Основную логику я писал и проверял под Windows, генерируя сразу изображение для 10-и экранов (это и есть те широкие изображения, которые последуют далее в статье), а время от времени я проверял решение на телефоне.

    “Результаты”


    Итак, линия горизонта (пока случайными блоками, даже не знаю, почему сделал случайными — пишу статью и сам с себя удивляюсь):

    Большие картинки кликабельны, но habrastorage немного уменьшил их размер (оригинал был 7200 х 1280)

    Теперь от случайного мусора, переходим к осмысленному содержанию. Линия горизонта «осмысленно»:


    Далее было необходимо создать “пещеры” (углубления в поверхности), чтобы рельеф не смотрелся так примитивно. Т.к. к тому моменту реализация была ещё сырая, то проверка алгоритма создания пещер представляла собой рисование “пещер” другой текстурой:


    Следующим шагом было решено разделить всё на передний и задний план, причём задний план должен был отличаться более тёмной текстурой:

    Это уже похоже на что-то, но ещё очень далеко от результата.

    Поменял текстуры, решил добавить внизу источник света — лаву, но ошибся с текстурой, поэтому низ был «заполнен факелами»:


    Исправляю и добавляю ещё один источник света — факелы:


    Понял, что соотношение блоков переднего и заднего плана меня не устраивает и поменял коэффициенты:

    Забегая вперёд, следует отметить, что это та часть, которую я менял много раз, чтобы добиться такого результата, чтобы успокоиться и больше не трогать его.

    Целью добавления источников света было более адекватное освещение — освещение от источников света. Источники света были поделены на три группы:
    1. Освещение от неба. Самый яркий источник света, но изначально была задумана смена времени суток, а значит и освещение от неба зависит от времени.
    2. Освещение от лавы. Менее яркий источник света, чем небо днём, но яркость не меняется во времени.
    3. Освещение от факелов. Наименее яркий источник света. Яркость также постоянна.

    В результате на освещение блока стали влиять два параметра — расстояние до неба и расстояние от статического источника света:

    Слева распространение света от источников, а справа просто “задний план темнее, чем передний”.

    Тут стала напрашиваться прозрачность, ведь нельзя оставлять факелы с белым фоном. Чтобы избежать вопросов “и в чём же проблема?”, напомню, что у меня есть массив одних циферок (пикселей) и других циферок (тоже пикселей) и все правила переноса и прорисовки необходимо было ещё написать. Хотели прозрачность — вот вам прозрачность (ещё чуть чуть прозрачнее и было бы необходимо рисовать картинку с камеры телефона, чтобы было достаточно “прозрачно”):

    Исправляем…

    Исправляя фон, случайно “закрасил” и пещеры землёй:

    Я знаю, что может показаться, что-то из этого было сделано умышлено, но уверяю вас, что все результаты были получены случайно. По правде говоря, я даже затрудняюсь сказать, как мне это удалось, но результат такой, какой он есть. Исправляем и это…

    Освещение блока за факелом вычисляется неверно (блок за факелом темнее, чем соседние блоки):

    Ошибка довольно глупая, но сразу я как-то и не подумал, что факел может быть в хорошо освещённом месте и считал освещённость блока максимальным значением яркости факела. Решений могло быть по крайней мере два — исправить освещение или убрать факелы из освещённых мест. Я решил исправить освещение.

    Теперь я решил, что необходимо сделать возможность указывать строку (seed), которая задаёт уникальную “карту”, а значит нужна была и своя реализация генерации случайных чисел (на самом деле не была нужна, т.к. хватило бы и обычного rand, но просился велосипед):

    и


    Вышло довольно “случайно”, если не сказать больше.

    Следующим шагом, были добавлены дополнительные блоки (этот шаг я упускаю, уж извините, и так очень много картинок), чтобы разнообразить внешний вид рельефа и тут пришла очередь деревьев.

    Про деревья я хочу отдельно сказать пару слов… К середине процесса разработки у меня уже был комментарий в коде, несколько записей на бумаге и одна пометка к скриншотам, примерно такого содержания: “i h8 3s”. И на то были причины. Деревья сразу пошли как-то сложно. Каждая мелочь, каждая правка кода обязательно сказывалась на деревьях. В целом, как бы смешно это не звучало, но самой большой занозой оказались именно деревья.

    Итак, первая итерация мучений с деревьями:

    Для начала было решено сделать ствол дерева и листья над стволом (пока без листьев по бокам), но даже тут я перепутал блоки и получил перевёрнутый результат.

    Исправил ошибку и добавил листья по бокам, причём снова не тот блок:


    В результате, после смены количества деревьев, получился вот такой результат:


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


    Потом последовал обычный для многих разработчиков quick-fix, без особого вникания в суть проблемы (ведь я же только что писал этот код, очевидно, что я могу его исправить не задумываясь!), что, как и можно было предположить, к положительному результату это не привело:

    Довольно любопытно то, что линия горизонта не совершенно ровная, а имеет небольшое искажение в начале.

    В результате я поэкспериментировал с разными параметрами и после множества результатов успокоился и перестал думать о исправлениях для линии горизонта. Вот несколько результатов для линии горизонта:


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

    А деревья всё продолжали огорчать — необходимо было сделать так, чтобы они генерировались только на земле, для объёма добавил затенение на некоторые блоки листьев (что кстати тоже к тому моменту не работало так, как хотелось бы):


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


    Тут меня почти моментально “осенило”, что же я сделал неправильно и новое исправление не заставило себя долго ждать:


    Тут следует сразу оговориться, что обычно я не пишу код в таком стиле (т.е. в стиле “сначала пишу, потом думаю”). Но в данном проекте я находил это очень забавным. Ведь каждая моя ошибка, каждая глупость, обязательно приводила к результату, причём очень редко я мог предсказать этот результат или сразу объяснить “почему так”.

    К этому моменту, текстура листьев и травы (земля с травой) была определённого зелёного цвета. Просилась реализация, которая позволяла бы менять цвет, позволяя малой кровью менять время года. Да, я отлично знаю, что это можно было легко сделать в джаве и не придумывать ничего, но спортивный интерес был слишком силён. Для этого, текстура была изменена и была написана функция для “покраски текстуры”:


    Саму функцию пришлось несколько раз исправлять, т.к. она создавала эффект разнокалиберного полосатика (два изображения в середине), но не делала того, что необходимо (правое изображение). Вот несколько результатов работы алгоритма:


    Время от времени, я правил те или иные методы, чтобы привести в порядок код и время от времени получал самые разные результаты. Ещё один из примеров:


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


    Во время отладки этой ошибки, когда у меня уже стали заканчиваться идеи, отладчик выдал вот такое значение цвета:

    В тот день отладку я оставил и решил отдохнуть, чтобы отладчик перестал рассказывать про плохое качество еды.

    На этом моменте я решил начать параллельно тестировать результат и на телефоне. В телефоне порядок цветов оказался немного другим, поэтому красная и синяя составляющие цвета поменялись местами:


    Кстати, я забыл лишний раз напомнить, что ненавижу деревья… Деревья ночью вели себя странно:


    В это время, у телефона вообще был свой собственный взгляд на то, как следует рисовать картинку после смещения (скролинга пальцем):
    и

    Ладно, цвет на картинке слева такой, потому что я забыл про положение синей и красной составляющей, а вот модный эффект motion blur — это уже “спасибо” android за то, что он совершенно верно рисовал моё изображение, у которого я не подумал про альфа канал (в альфа канале к тому моменту могло быть всё что угодно).

    Кстати! Давно я не показывал Вам свои деревья! Вот:

    Проблем тут довольно много — и неверное освещение некоторых блоков листьев, и неверная прорисовка “прозрачных пикселей”.

    Параллельно я начал работу над системой waypoint’ов или, говоря простым языком, алгоритмом поиска путей. Путь был нужен, чтобы была возможность добавить зомби и прочих персонажей и при этом не пришлось бы каждый раз просчитывать их поведение на несколько шагов вперёд (чтобы они не тупили на месте). Для себя я стал отмечать пути визуально, чтобы оценить качество алгоритма:


    Более продвинутый вариант визуализации смотрелся вот так:


    Кстати, зомби я в результате так и не добавил (не дошли руки), но систему вэйпойнтов отладил. Обратите внимание и на деревья на этих двух картинках. Деревья всё ещё смотрелись прекрасно…

    В какой-то момент, когда я пытался исправить внешний вид деревьев, получил ещё один “положительный”:


    Вот ещё несколько любопытных багов с телефона, которые напрямую связаны с прозрачностью (альфа каналом):
    и

    Затем были и ошибки из-за добавления дополнительных текстур (а значит и сменой индексов текстур):


    Потом я “поправил” что-то в алгоритме прорисовки и получил довольно странный эффект (скорее всего напутал с размером и положением текстур):


    В результате последних оптимизаций алгоритма прорисовки, я получил сразу две ошибки (снова разнокалиберные полосатики):
    и

    Процесс прорисовки этого чуда смотрится так (очевидно, что это самый оптимальный вариант):


    Последней вещью, которую было необходимо реализовать, был эффект обьёма (грани блоков), чтобы картинка не была такой плоской и простой. Для граней необходимо было написать два алгоритма — алгоритм изменения размера картинки и алгоритм наклона изображения.

    Начну с алгоритма уменьшения размера картинки:


    Сверху вниз — результаты работы алгоритма. В самом низу — желаемый результат (тест проводился на большой картинке, для наглядности).

    Больше отличился алгоритм поворота:


    Слева наверху — начальное изображение, а справа внизу — желаемый результат, а в середине всё то, что получалось по пути к результату.

    Когда алгоритмы были готовы, сделать объём уже было довольно просто. Блок составляли 3 грани (взгляд с одной стороны, псевдо-3д или так называемый 2.5d). Для красоты на грани был нанесён линейный градиент, который тоже пришлось отладить, чтобы получить желаемый результат:


    Поскольку с размером граней блока я так и не смог определиться, сделал возможность изменять этот параметр:


    После всех изменений, было необходимо оптимизировать процесс прорисовки, чтобы не отрисовывать ничего из того, что не нужно рисовать. Результатом этой оптимизации снова стала проблема с каналом прозрачности, которая всплыла во время тестов на телефоне. Должен признать, что полученный результат смотрелся довольно любопытно:
    и

    Результатом исправления этой ошибки стал не столь интересный эффект:


    Итоги

    В конце концов все критические ошибки были отлажены, добавил текстур и плюшек в виде эффектов. Были сделаны две версии для стора — бесплатная (очень упрощённая) и платная (со всеми плюшками).

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

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

    Спасибо тем, кто хоть долистал до конца статьи!
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 37

      0
      Вы молодец! Было интересно почитать, спасибо!
      Но один вопрос все же мучал меня всю статью: почему не стандартные текстуры из майна?

      UPD: Написал коммент и только после этого заглянул в маркет и увидел там, что приложение также для продажи, так что вопрос снимаю :)
        0
        Я сразу решил, что не буду использовать ни OpenGL, ни ещё чего-то, что помогло бы мне реализовать задачу — только хардкор.
          0
          Стандартную текстуру я включил в конечное решение (как вариант), а другую текстуру я выбрал только из-за более высокого качества текстуры (32х32 вместо 16х16), что лучше смотрелось на экране телефона.
            +1
            Скачал и убедился :) Но я бы посоветовал убрать стандартные текстуры, они, кажется, являются собственностью Mojang и запрещены к использованию в коммерческих целях. Но я могу ошибаться.
              +1
              Учитывая политику Mojang, я думаю что, они не станут что либо предпринимать против автора.
          +4
          Скрин игры а-ля «Клад» выбил скупую мужскую ностальгическую слезу…
          +2
          Ох, сразу захотелось написать свой платформер, спасибо!

          offtop
          Подскажите пожалуйста какой-нибудь хороший мануал по физике в 2d. Давно гложит меня идея попробовать написать 2d платформер на javascript, но каждый раз останавливает полнейшее непонимание азов физики в играх :(
          /offtop
            0
            Я бы посоветовал смотреть gamedev ресурсы, ведь по сути это именно то, что Вы бы хотели сделать. Я не хочу рекламировать ни один из ресурсов, но скажу, что найти их по «gamedev» очень просто (причем и на русском языке довольно много неплохих статей на эту тему).

            Кстати, на хабре тоже довольно много интересных статей на эту тему.

            Надеюсь вскоре увидеть рассказ о том, что у Вас получилось!
              0
              Есть даже статья на хабре: JavaScript Gaming: Часть 1. Box2d и основы Физики
                0
                Ага прочитал, спасибо.
                Теперь все стало еще сложнее /*_*/
                Оказалось помимо box2d существует уже 100500 библиотек для этих целей.

                И как я понял самые развивающиеся это:
                box2d
                p2
                pixi

                Не подскажите, что выбрать, с чего начать? Может вообще не туда смотрю…
                  0
                  Да даже реализаций box2d на js около 5 штук, какую именно выбрать не подскажу, я далек от JS. Но однозначно стоит выбрать box2d, это почти стандарт в 2d играх. Он есть на всех языках, и очень много инфы в интернете, я когда делал игру под ios, смотрел гайды по флешу, проблем не возникло.
                    0
                    Спасибо, действительно везде пишут, что api в либе одинаковое, ну или почти одинаковое в разных реализациях.
              +6
              Вот примерно так мне кажется, Террарию и написали.
                0
                Там XNA основную работу делает.
                  0
                  Я не в технологическом плане, а скорее в плане подхода и шагов, уж больно идеи двумерного майнкрафта похожи и алгоритмы генерации.
                    0
                    Там страшная жуть при отрисовке и кешировании состояний блоков :)
                +2
                Блин, это нереально круто! :)
                Спасибо вам большое за вдохновение.

                Очень хочу подробности про техническую сторону.
                  +1
                  Спасибо за статью!
                  Вспомнилось как я когда то на Pascal воду пытался имитировать.))
                    +1
                    Почитал с удовольствием)
                      +1
                      Да, про техническую сторону тоже было бы очень интересно почитать.
                        +1
                        Спасибо за статью! Очень интересно и легко читается!
                          +4
                          Вау… Метод проб и ошибок, дааа… Поражаюсь терпению автора. Вспоминаю, когда я пытался сделать свой первый более-менее сложный по дизайну сайт на HTML и CSS. Тут поправлю — там вылезет. Там поправлю — ещё хуже стало. Вернул всё на место и сделал небольшую поправку — И ТУТ КРОВЬ КИШКИ ВЁРСТКА БЛОКИ РАЗЪЕХАЛИСЬ СТИЛИ СЛОМАЛИСЬ К ЧЁРТОВОЙ МАТЕРИ. А потом находишь решение. Почти рабочее. Только вооон там подвинуть. Двигаю, И ТУТ ОПЯТЬ ОШИБКА В СТИЛЯХ БРАУЗЕР ПОЛЕТЕЛ УВЛЁК ПОЛСИСТЕМЫ КОМП ФОРМАТНУЛСЯ МОНИТОР ВЗОРВАЛСЯ ИНЕТ ОТКЛЮЧИЛСЯ. Так и не полюбил я вёрстку.
                          Чувствую я, «обожаю программирование графики» в заголовке написано кровью и с огромной долей иронии =D
                          +1
                          А сколько времени у вас занял весь процесс?
                            0
                            Я затрудняюсь посчитать время, т.к. делал довольно много перерывов в разработке, да и не всегда было время или желание продолжать. Но общее время, с учётом перерывов на месяц-два и периодов, когда в неделю тратил по часу на этот проект, получилось около 7 месяцев.

                            Но это именно общее время от начала до конца, а не само время разработки.
                              +1
                              ну меня именно оно интересовало! А то написано у вас так просто и легко: «Начал делать систему waypoint'ов. *через строчку* Сделал!»- будто вы все за 2 вечера сделали — так я себя так уныло почувствовал.
                                0
                                Вы знаете, на самом деле, реализация самой системы вэйпойнотов пожалуй и заняла пару вечеров, т.к. по сути нужно было найти точки входа-выхода (места, где в пешерах есть стена в два блока в высоту), от них найти все проходимые пути (соседние блоки, которые имеют над собой свободное пространство высотой в 2 и больше блоков и до которых можно добраться, т.е. изменение выосты не более ±1 блок в высоту за шаг ). А дальше просто следовало расставить вес для мест, где можно упасть, но нельзя подняться и получаем довольно простую задачу на поиск всех путей в графе.

                                Мне кажется, что я потатил больше времени на визуализацию вэйпойнтов, чем на сам алгоритм построения их. Но тут уж стот заметить, что алгоритм на самом деле довольно прост.
                            +1
                            bugreport://
                            Если медленно прокручивать «домашний экран», ландшафт не прокручивается ;]
                              0
                              У обоины нет чёткой привязки к экранам ланчера, но зато работает одинаково под многими ланчерами. Поэтому, я бы сказал, что это «фича».
                              +1
                              Из идей на развитие — было бы классно добавить потоки солнечного света в пещерах, картинка бы здорово оживилась :)
                              В любом случае спасибо за статью! Очень напомнило времена когда делал простенькие игрушки на quickbasik+asm…
                                +1
                                Удивительно увлекательная статья, да ещё и так в тему (%. Спасибо, и пишите ещё.
                                  0
                                  сложилось устойчивое ощущение что автор не знает чем BYTE отличается от DWORD и что такое 24bpp 32bpp или 16bpb
                                  слово PALETTE наверное вгоняет в доисторический УЖОС.
                                  RGB и RGBA навеное тоже не входит в лексикон.

                                  имхо нубство на нубстве и нубством погоняет.
                                    0
                                    Что именно натолкнуло на такие мысли? Вы считаете, что возможно реализовать всё вышеописанное на чистом C++, не зная этих понятий?
                                      +1
                                      не знаю. я же сказал что создалось ощущение.
                                      оценочное суждение как модно сейчас говорить.
                                      если это не так то приношу свои извенения.

                                      наверное из-за непосредственной радости от перестановки местами каналов и чтение 32bpp растра как 16bpp %-)
                                      да еще и без учета выравнивания BMP формата
                                        0
                                        Статья не техническая, а скорее просто рассказ с попыткой и пошутить, и посмеяться над собой (поэтому и может сложиться впечатление, что всё так запущено). Но за комментарий спасибо.
                                          0
                                          ну если так то вы своей цели добились — я честно признаться улыбнулся. и еще раз извиняюсь за неверное суждение.
                                          надеюсь вас оно не очень сильно обидель.

                                          ЗЫ думаю немноггим но будет полезно сделать камменты под «кривыми» растрами почему так получилось и что делать чтобы так не было

                                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                  Самое читаемое