| Разработка и ромхакинг > Программирование |
| Программирование редакторов для внесения каких-то изменений в ром. |
| << < (2/3) > >> |
| SeregaZ:
Итак в памяти у нас висит картинка, но она 24 битная и к нашим черным делам не подходит. Надо её извратить и посвятить в нашу веру. А значит переходим ко второму этапу извращений, а именно вписывания 24 битного изображения в 15 цветов нужной палитры. Честно говоря я без понятия как это делается :) Мне дали кусочек кода погонять (привет моему буржуйскому товарищу Wilbert'у). Я без понятия что за магия там происходит - главное что происходит (причем там код в asm, но понятное дело что не наш 68к асм и тем не менее!). Суть в чем - указываем палитру, а после гоняем изображение попиксельно и алгоритм сам подбирает наиболее подходящий цвет. То есть берет 24 битный пиксель и подбирает какой-то из 15 цветов в нашей палитре, который ему соответствует наиболее близко. Сам этот код функции в проекте, к сожалению, будет без комментариев. Так как я не очень понимаю чего они там делают и уж тем более в асме - двойная тайна :) Этот код объявлен как модуль (опять-таки хрен его знает в чем принципиальная разница между функцией и модулем - по мне те-же яйца, только в профиль) и соответственно вызов там несколько отличается. Что-то там два двоеточия нужны вроде как... ну не суть. Новые команды, которые будут использованы: Point(x, y) Дает нам цвет в заданной точке. То есть если рисовать надо точкой Plot, то вот читать номер цвета надо Point. Так... в общем пришлось все-таки сделать счет цветов с 0, а не с 1 как я хотел. А то это этот самый модуль под подбору цветов брыкается. Так-что изменения в коде будут во многих местах по отношению к предыдущей версии. По итогу взял в гуглах иконку Тимоти Шалопайме и втулил в нашу супер мега программу. |
| SeregaZ:
Получается у нас в программе висит подготовленное изображение с сеговскими цветами. Осталось дело за малым - сохранить его в совместимом формате, который приставка поймет. Схема будет точно такая-же как при чтении сеговского файла иконки ранее - процедура, но только чтения цветов, а не рисования и её мы будем вызывать передвигая координаты по изображению сверху вниз и как доходит до низа, поднимаем вверх и сдвигаем вправо... 1 5 9 13 2 6 10 14 3 7 11 15 4 8 12 16 Так-же держим в уме, что в одном байте сидят два пикселя, а не один. То есть нам понадобится наш любимый сдвиг << на 4 байта влево. Так-же нам понадобится Point - чтение RGB номера цвета из изображения. Потом надо будет прогнать этот цвет по сеговской палитре в 16 цветов, чтобы узнать какой номер у этого цвета. То есть сохраняется в файле номер цвета, а не сам код цвета. Вот поэтому, что два пикселя в одном байте и сидит это самое ограничение на 16 цветов в одной палитре. Ведь что такое байт - это 8 бит: 255 десятичное = $FF хекс = %11111111 бинарное вот смотрим на этот самый хекс $FF - F и F, или бинарное %1111 и 1111, то есть это 15. если 0 тоже считать за человека - то и выходит как раз 16 значений - 16 цветов - точнее 16 номеров цветов - еще точнее 16 номеров в массиве, где лежат коды цветов палитры. типа у нас была палитра с номерами цветов 1. синий, 2 красный, 3 серобуромалиновый... после мы получаем цвет пикселя из изображения. и скажем он красный - то есть получается красный это номер 2 в палитре. сразу же смотрим соседний пиксель - он серобуромалиновый - то есть 3. тогда получается байт в итоговом файле будет выглядеть как $23 а получился он путем сдвига и сложения 2 << 4 + 3 или даже чтоб понятнее: $2 << 4 = $20 (двойку сдвинули на 4 бита влево) $20 + 3 = $23 (и прибавили 3) Для эксперимента я вставлял ту-же самую иконку биосканнера и сохранял. После открыл в хекс редакторе оба файла и оказалось... что они совпадают! Надо-же! Так неожиданно! (ладно, отставить сарказм. на самом деле может быть так, что файлы с одним и тем-же изображением по итогу несовпадут. все дело в одинаковых цветах. если скажем в палитре нулевой цвет, который прозрачный был черным, и скажем номер 8 цвет тоже был черным, то при таком подборе цветов система, увидя черный цвет - сохранит его под 0 номером. все черные цвета будут 0 и тогда файлы не будут совпадать, ведь в оригинале там должны быть 8) |
| SeregaZ:
Домашнее задание про проекту: Вникнуть во всю писанину кода, что в данной теме была расписана и для себя выделить в отдельный проект: открыть файл карты выделение памяти по размеру этого файла запись в память образа файла закрыть файл В принципе в файле хелпа PB есть подобный пример, но там комментарии буржуинские и будет не совсем понятно. Нас интересует именно что не открытие файла и чтение с жесткого диска, а именно чтоб в память образ запилился. Результаты отпостишь тут. Дам квест номер 2. |
| SeregaZ:
ну я бы понял если там файлы с кодом-текстом, где машина может обработать - нахрена сотни скачиваний GEMS банков с мелодиями из темы с архивами мелодий игр? их же машина неосилит :) это очень специфический продукт - это даже не VGM. так что видимо китайские боты накрутили. просто от нечего делать. |
| dimidrol:
Ну отсюда проджект я скачивал пару раз, но то не охота, то некогда азы барсика постигать, поэтому и молчком. |
| SeregaZ:
ты квест выполнил? где код? >:( |
| Cyneprepou4uk:
https://www.romhacking.net/forum/index.php?topic=39581.0 Чел написал универсальную прогу для редактирования данных в играх. |
| SeregaZ:
универсальную прогу... надо программировать :lol: там тоже самое по сути - сначала нужно знать где что лежит, а потом уже придумывать как это редактировать. эту прогу не качал и осуждаю, но судя по скринам графония там не касаются. хотя и у меня только самое самое начало затронуто с графонием. и то причем спрайтовым. экраны в планах были, но все так и незасел наваять эти примеры для темы. но опять таки - нужно заранее знать где эти экраны лежат - где тайловые сеты, где тайловые карты, где палитры... в идеале то конечно было бы здорово сделать некий универсальный инструмент, где как пазлами выстраиваешь нужные кнопки, назначаешь им действия - типа эта кнопка выдает список врагов, эта кнопка выдает форму с кучей окошек с параметрами монстров, эта кнопка вызывает редактор иконок, эта кнопка редактор экранов... но это несколько не то. суть не в том, чтобы сделать готовый универсальный инструмент - суть научится программировать, чтобы самому такие не сложные редакторы делать, где все легко понятно и доступно, нежели вручную сидеть страдать над хекс редактором три дня. я например пришел в ужос при просмотре видоса, где с помощью хекс редактора изменяли положения гербов в Дюне. не помню чей видос был :) как бы с одной стороны - да, более низкий подход, требующий более обширные знания пользователя... но с другой это же просто пипец какие костыли! в моем случае все мышкой двигается, сохраняется и экспортируется и главное я остался доволен процентов на 70 от того как получилось. хотелось бы конечно чтобы каждый элемент на экране можно было бы двигать отдельно, а не большими блоками... но тут уж как смог :) все лучше, чем в хекс редакторе вручную... |
| SeregaZ:
Экраны. Часть первая, теоретическая, всем известная. можно сразу переходить ко второй - практической части. Если со спрайтами все понятно - мелкие фрагменты от 1х1 тайла до полноприводных 4х4, то с экранами маленько сложнее. Эти самые экраны состоят из трех частей, ладно, четырех: 1. палитра 2. тайловый сет 3. тайловая карта 4. режимы работы С палитрой и отчасти с тайловым сетом мы встречались еще во времена спрайтов и там как бы все понятно, но что еще за тайловая карта? Это по сути матрица, с инструкциями как на ней расположены тайлы друг за другом. Там каждому тайлу на изображении дополнительно указываются его параметры: номер тайла из тайлового сета приоритет палитра зеркало по Х зеркало по У Эти параметры заключены в двух байтах. то есть для каждого тайла в тайловой карте выделено по 2 байта. Чтобы было понятнее надо эти два байта показать в бинарном виде: Конечно на данном этапе возникнет вопрос: да нахрена это надо? просто загрузил PNG картинку и пользуйся! С точки зрения современного пользователя, насмотревшегося всяких 4к на ютубах это прям очень даже не понятно, но вот в те времена, когда трава была зеленее - железо только только начинало развиваться и поэтому тогдашние инженеры выкручивались как могли чтобы сделать и подешевле и чтоб возможности были вполне себе конкурентными, чтобы нинденду обниндендить. Вот и пришли они к тому, что приставка будет иметь 4 палитры - то есть 2 бита из тех 16 бит в тайловой карте (00, 01, 10, 11 - 0, 1, 2, 3 номера палитр) и 11 бит под тайловый номер (а 11 бит это %111 1111 1111 или $7FF в хекс или 2047 в десятичных - 2047 возможных тайла) В принципе такая куча тайлов не так уж и мала и есть куда разгуляться... НО! под видеопамять у нас выделено всего 65 килобайт в приставке. Плюс еще помимо самих тайлов в этой-же памяти должны болтаться две тайловые карты под два слоя картинки и еще должна болтаться таблица спрайтов. Получается полноценно втулить изображение может нехватить памяти - ведь под каждый тайл надо по 32 байта (напомню: в одном байте 2 пикселя и поэтому 8 строк помножить на 8 столбиков = не 64, а 32). Вот с точки зрения спрайтов, где тайлы должны идти друг за другом - все плохо. А вот с точки зрения экранов - маленько получше, так как тут инженеры припасли трюки ушами. Во первых - кто сказал, что нумерация тайлов обязательно должна быть 1 тайл, 2 тайл, 3 тайл, 4 тайл, 5...... ? Можно оптимизировать изображение. Совсем необязательно чтобы у картинки были все тайлы уникальные. Можно взять и сделать сравнение тайлов и если 1 и 2 тайл одинаковые, то значит можно в тайловой карте для второго тайла указать то, что он будет использовать 1 номер тайла и картинка будет точно такая-же как и была, то есть ничего не потеряет. Тоже самое касается каждого тайла относительно всех остальных тайлов на изображении - надо прогнать по всей картинке и выкинуть повторяющиеся тайлы. Иными словами - эти тайловые карты позволяют нам сжимать изображение. В результате тайловый сет может занимать меньше памяти, чем ежели бы он был без этого "сжатия" напрямую. Второй трюк заключается в использовании зеркал, что может сжать тайловый сет еще больше, то есть тайл может быть перевернут на изображении - скажем по вертикали, или даже и по вертикали и по горизонтали. Вот на флаге моей страны - Казахстана есть повторяющиеся элементы. Если в первом случае все тайлы уникальные это займет 32 тайла, а во втором повторяющиеся будут отсечены - их станет 14. А если еще проверить тайлы на зеркала, а в данном случае по вертикали - то можно еще 2 тайла отсечь и по итогу тайлов станет 12. Вот в этом суть использования тайловых карт. С палитрой тоже все понятно - каждому тайлу в тайловой карте можно указать какую именно палитру он будет использовать. А бит приоритета регулирует где этот тайл на экране будет находится по отношению к спрайту. Где-то было видео из Дюны, где автор рассказывал про этот приоритет и демонстрировал, как юнит мог заехать в заросли - то есть элемент ландшафта мог визуально спрятать юнита от глаз игрока (радару и компьютеру конечно пофиг - найдет и покарает в любом случае :)) Или если это игра-бродилка, то таким приоритетом можно было бы рисовать колонны в здании например, которые бы скрывали персонажа во время ходьбы, добавляя сцене глубины. Что касается режимов работы экрана, то это касается размеров экранов в зависимости PAL и NTSC, а так-же выбранного режима - H40 или H32. Тут позволю себе копирнуть из интернетов: NTSC: Режим H40 — 320x224 пикселя (40x28 тайлов). Самый распространённый режим разрешения Режим H32 — 256x224 пикселей (32x28 тайлов). Менее популярный режим разрешения PAL: Режим H40 — 320x240 пикселей (40x30 тайлов). Самый распространённый режим разрешения Режим H32 — 256x240 пикселей (32x30 тайлов). Менее популярный режим разрешения |
| SeregaZ:
Часть вторая, практическая. Несмотря что популярный размер это NTSC H40 это 320x224 пикселя - тайловая карта (обычно) бОльшего размера - 512х224, просто правой части экрана не видно. При условии конечно что это статичный экран, а не арена MK3, где можно ходить блудить туда сюда. И поэтому обычно тайловая карта экрана имеет больший размер в роме, но могут быть частные случаи типа как ZT - там тайловая карта ровно 40 тайлов в ширину. Поэтому там при загрузке нужны дополнительные танцы с бубном, со вписыванием такого 40 тайлов ширины экрана в 64 ширинный экран, с невидимой справа частью... но не суть. Мы пока про обычные, чаще встречаемые случаи. Поэтому размер экрана я сделал не 320, а 512 пикселей, чтобы тайловая карта рисовалась вся как есть. Так-же для первого, так сказать, опыта работы с экранами - тайловая карта не использует зеркала. Код с зеркалами мы добавим позже. Что нового, что ранее не встречалось: Битовые операции. Для того, чтобы вычленить из 2 байтового значения нужные нам параметры можно использовать сдвиг и маску. То есть к примеру было у нас значение и нам в нем нужна палитра: %1010111110101111 Пришлось бы сдвигать значение вправо, а после накладывать маску %11. Как бы действия то все понятны... но не совсем удобно. Вот тут то и появляются эти самые битовые операции: --- Код: ---;{ битовые операции Macro SetBit(Var, Bit) ; Установка бита. Var | (Bit) EndMacro Macro ClearBit(Var, Bit) ; Обнуление бита. Var & (~(Bit)) EndMacro Macro TestBit(Var, Bit) ; Проверить бит Bool(Var & (Bit)) EndMacro Macro NumToBit(Num) ; Позиция бита по его номеру. ; для вызова сет бит и клер бит. (1<<(Num)) EndMacro Macro GetBits(Var, StartPos, EndPos) ; Прочитать от и до ((Var>>(StartPos))&(NumToBit((EndPos)-(StartPos)+1)-1)) EndMacro ;} --- Конец кода --- Опять таки в чем разница между процедурой и макросом - да черт её знает... главное что работает :) Теперь вместо сдвига и маски будем писать так: --- Код: ---palnum = GetBits(tmapvalue, 13, 14) --- Конец кода --- То есть берется значение tmapvalue и из него выковыривается значение из двух бит, начиная с 13 по 14 бит - то самое место, где лежит палитра. Точно по такой-же системе получаем номер нужного тайла в тайловом сете: %1010111110101111 --- Код: ---tnum = GetBits(tmapvalue, 0, 10) --- Конец кода --- То есть значение с 10 по 0 бит. А зеркала и приоритет будем возвращать путем TestBit %1010111110101111 --- Код: ---vertical = TestBit(tmapvalue, NumToBit(12)) --- Конец кода --- То есть вертикальное зеркало у нас лежит в 12 бите. Дальше вроде все уже знакомое и встречалось ранее. Просто берем и как пазлы собираем нужные кусочки из прошлых проектов и втуливаем сюда. По итогу вышло примерно так: |
| SeregaZ:
тут это... затык :lol: вопрос по ромхакерской направленности. вспомнил что у меня есть тестовой ром, показывающий картинки, но он требует 40х28 тайловые карты. и соответственно в процессе работы конвертирует 40х28 в 64х28. в моем же случае карты уже 64х28. как их втулить то? уже и комментил функцию и чего только не делал - не хочет :) там что-то типа: --- Код: --- lea pic_map1,a0 move.l #$40000003,(a2) ; VDP PORT ADDRESS VRAM $C000 - MAP1 move.w #$0001,d3 bsr.s load_map_40x28 load_map_40x28: ; map 40x28 -> 64x28 conversion moveq #27,d2 moveq #0,d4 @copy_n moveq #63,d1 ; 39 @copy_l move.w (a0)+,d0 add.w d3,d0 move.w d0,(a1) dbf d1,@copy_l moveq #0,d1 ; 23 @fill_l: move.w d3,(a1) dbf d1,@fill_l dbf d2,@copy_n rts --- Конец кода --- полагаю что-то вместо load_map_40x28 надо вписать, чтобы оно pic_map1 (64х28 карта) втулило в нужный кусочек памяти - там где тайловая карта должна болтаться. у меня не выходит :( |
| Sharpnull:
--- Цитата: SeregaZ от 05 Ноябрь 2024, 20:48:32 ---соответственно в процессе работы конвертирует 40х28 в 64х28. в моем же случае карты уже 64х28. как их втулить то? --- Конец цитаты --- Попробуйте так: --- Код: ---load_map_64x28: moveq #27,d2 @copy_n moveq #63,d1 @copy_l move.w (a0)+,d0 add.w d3,d0 move.w d0,(a1) dbf d1,@copy_l dbf d2,@copy_n rts --- Конец кода --- Т. е. я убрал цикл @fill_l, который добавлял тайлы для заполнения (d3, он же базовый для остальных). UPD: Или короче: --- Код: ---load_map_64x28: move.l #64*28-1,d1 @copy_n move.w (a0)+,d0 add.w d3,d0 move.w d0,(a1) dbf d1,@copy_n rts --- Конец кода --- UPD2: вместо move.l #64*28-1,d1 можно move.w #64*28-1,d1, потому что в dbf только 16 бит учитываются от регистра. |
| SeregaZ:
заработало, спасибо :) Добавлено позже: Было бы неплохо маленько добавить визуальное разделение на экране. Типа сделать вертикальную зеленую полоску границы экрана на 320 пикселях и добавить сетку тайлов 8х8 пикселей. Полупрозрачных причем. Еще наверное надо добавить второе большое окно, где отображались бы эти самые зеркала и приоритет. Наверное сделаю тайл чуть ярче чем черный, если приоритет 1. И добавлю значек х или у если были зеркала. Рисовать будем классической бейсиковской командой Line. Там не сложно сначала стартовые координаты точки откуда линия рисуется, дальше относительные координаты в какую сторону от старта двигаться. А чтобы сделать линию полупрозрачной - надо вместо RGB указывать RGBA, где четвертый параметр как раз таки прозрачность. Так-же режим рисования надо будет переключить на DrawingMode(#PB_2DDrawing_AlphaBlend) Ну и поскольку отображение этих полосок мы сделаем включаемо-отключаемыми, то значит и перерисовка нам будет нужна несколько раз, а для этого чтобы не повторять один и тот-же код по писят раз в программе - сделаем рисование, то есть обновление гаджета, в процедуре. И будем вызывать эту процедуру когда нам надо - сразу после отключения-включения галок. Получить статус чекбокса можно с помощью GetGadgetState. Ага... в процессе запиливания стало понятно что зеленый цвет на голубом фоне флага виден не очень. Значит надо сделать переключатель какого цвета будет граница экрана и самих тайлов. Типа зеленый я обычно леплю, но можно и черный и фиолетовый к примеру. И изначальный выбор зеленого цвета надо будет указать на гаджет зеленого путем использования SetGadgetState. Ну и раз пошла такая пьянка, то хорошо бы сразу сделать кнопку со сборкой рома через ASM68K.exe, и потом запуск эмуля, где можно было бы поглядеть как оно будет выглядеть в итоге. Для чего понадобится RunProgram. Ну и для переменной EmulatorPath$ надо будет указать свой путь до эмулятора, иначе волшебства не произойдет. Ну и может быть такой вариант, что сборка рома ASM68K.exe зафейлится и тогда мы запиливаем диалог ошибки с возможностью выбора запуска бат файла отдельно. Дело в том, что при запуске ASM68K.exe окошко мелькает и сразу-же закрывается, не давая прочитать любовные письма, которые он там пишет. Как бы PB имеет функции чтения чужого консольного окна, но затык в том, что ASM68K.exe не дает прочитать, скатина. Гранаты там не той системы и соответственно лог работы ассемблера не дается. С большинством же других консольных программ нет никаких проблем. Что касается самих зеркал, то там используются те-же самые циклы, как при рисовании обычного тайла без зеркал. Просто мы меняем значения от и докуда крутить местами и в самом цикле указываем направление Step -1, то есть от большего к меньшему. Ну и в случае с Х зеркалом x - 1 для второго пикселя, вместо х + 1 как было при обычном тайле без зеркал. В конечном итоге дизайн окна теперь выглядит так: |
| Yoti:
--- Цитата: SeregaZ от 05 Ноябрь 2024, 23:04:23 ---Ага... в процессе запиливания стало понятно что зеленый цвет на голубом фоне флага виден не очень. Значит надо сделать переключатель какого цвета будет --- Конец цитаты --- Почему просто не инвертировать цвет? |
| SeregaZ:
ну... проще все-таки обычная линия, нежели еще один цикл, где бы проверялась каждая точка на пути этой линии, инвертировался цвет этой точки и перерисовывался. нужен как можно простой код, чтобы новичку было легче вкатывать в тему. а там уже когда наш подозреваемый втянется в вопрос и откроет тайну вселенной, что программировать на самом деле не сильно сложно - то сам уже сможет и инвертирование и блек джек и легкодоступных женщин там напрограммировать. |
| SeregaZ:
в общем тяжело что-то там родил... алгоритм вставки такой: 1. 24 битное изображение 320х224 пикселя сначала перекрашиваем в 512 сеговских цветов. 2. измеряем вес пикселей, количество пикселей одинакового цвета на изображении и отбираем 16 с самым большим весом в палитру. 3. перерисовываем 512 цветное изображение в 16 подготовленную палитру. Чтобы вставить картинку, надо в пейнте вырезать область размером 320х224 и нажать вставку на экране программы. Если результат нормальный - можно нажать сохранить, а потом кнопку тест и увидеть результат уже в эмуляторе. Итоговый результат конечно такой себе :) В архиве как пример железный человек лежит. При его вставке - весь красный цвет идет по бороде и изображение преимущественно упарывается в синий оттенок. Палетквант из интырнетов сделает в тыщу раз лучше :) Поскольку я совсем растерял навыки, а код брал из другого места, который до этого мои кривые руки на что-то там правили - вполне мог напортачить :) Надо было оригинальный буржуинский код найти и его копировать, а не мою недо-переделку. Я забыл уже все нюансы... так что местами в коде ашипки. Надо опять по новой вникать. И надо еще подумать над палитрой, чтобы в ней в любом случае присутствовал черный цвет как первый в палитре. Как раз в моей доделке оно и было... но я забыл как это включать :) Сейчас черный цвет может уплыть или не быть вовсе в палитре и поэтому правая часть окна может быть не черной, как изначально с Дюной. Пока-что в изображении не выкидываются повторяющиеся тайлы, то есть изображение сохраняется целиком. Это я потом добавлю. А вот другие две вещи - было бы неплохо подумать, как сделать. 1. сжатие графики, по типу МК3. декомпрессор-компрессор как ехешку с форума - не предлагать. надо прям по детски объяснение как это работает, чтобы я мог сделать что-то похожее "по мотивам". 2. прямая запись в эмулятор образа рома. именно не как файл, а прям кусман памяти вписать в эмулятор с ромом, дабы не создавать всякие мусорные временные файлы на жестком диске (у меня просто пунктик на эту тему). В код добавлен не мой модуль по подбору цветов и пожатии палитры из 24бит в заданное количество. Это очень много кода. Он там во многом без комментариев, ибо я сам хрен знает что там происходит. Но во всех остальных местах комментариев написал как можно больше с объяснениями. И да! Надо опять таки в коде исправить путь до своего эмулятора. Там сейчас мой путь и кнопка ТЕСТ работать не будет если не исправить. Можно конечно через ини файл сделать. Типа при первом запуске потребует путь, а после уже будет всегда знать где эмулятор лежит. Так-же путь до рома тоже надо будет подумать как доделать. |
| SeregaZ:
родил :cool: . 2 дня убил, но вроде раздуплил. есть и код на PB с сжатием тайлового сета и на ASM разжатия и записи в VDP. ляпота! теперь можно подумать как втуливать образ памяти в эмулятор, чтобы не создавать временный файл рома на жестком диске. бегло глянул - пока не ясно. нашло пяток адресов, где вроде как ром сидит в памяти эмуля. надо будет тестить, вникать. *** Итак. Теория выкидывания повторяющихся тайлов. Сейчас пишет тайлы прям друг за другом, как оно идет на изображении. Алгоритм будет такой: 1. создаем сложный массив, как ранее мы делали для палитры. там одна ячейка будет состоять из 4 ячеек-образов в памяти по 32 байта. 2. при создании тайлового сета следует изменить немного логику: 2а. самый первый тайл в любом случае пишем в наш сложный массив, в первую из четырех ячеек, как есть. 2б. запускаем процедуру переворота тайлов и пишем готовые результаты в 2-3-4 ячейки этого-же элемента массива. то есть с зеркалами по Х, У, и оба зеркала. 2в. начиная со второго тайла прогонять через сравнение памяти весь этот массив, добавляя если совпадений не было. недобавляя, но отмечая галку зеркал в тайловой карте - если какой-то из тайлов в массиве совпался с текущим. Получается образ в памяти тайловой карты и тайловый сет надо будет делать одновременно. Схемы переворотов тайлов были выше (картинку прикладывал). В теории тут все понятно, а вот с точки зрения циферок надо будет мудрить. Так как в 1 байте там по 2 пикселя, то есть каждый байт надо будет переворачивать. Где-то в старых проектах я это уже делал. Как найду где - скопирую оттуда и приложу к проекту. Таким образом у нас будет выкидывание повторяющихся тайлов. Хотя наверное в начале надо было сделать просто повторяющихся, а в следующей итерации кода - повторяющиеся и зеркальные, чтобы было проще в тему вникнуть. Сразу с обоими - как есть и зеркальными - код будет сложнее. |
| SeregaZ:
Итак, та часть где происходит переворачивание инфы. Сделал через "жесткий" массив, это там где --- Код: ---Array[x] --- Конец кода --- То есть он как бы всегда одинаковый размер имеет. Такой-же массив, но с круглыми скобками --- Код: ---Array(х) --- Конец кода --- может изменять свой размер с помощью ReDim. По итогу при запуске кода в окошке дебага будет высвечен текстом "тайл" без зеркал, и ниже с зеркалами для понимания, что должно происходить при зеркалировании. --- Код: ---; сложная структура массива для тайлов, чтобы организовать выкидывание повторяющихся тайлов Structure usedtilesstruct original.a[32] ; .a[32] - жесткий массив на 32 байта xmirr.a[32] ymirr.a[32] bothmirr.a[32] EndStructure Global Dim UsedTiles.usedtilesstruct(1120) ; .usedtilesstruct(0) - изменяемый массив со структурой Procedure CreateMirrors(arrayitemnum.u) ; у нас есть оригинальный тайл - UsedTiles(arrayitemnum)\original ; нужно просто перевернуть его данные для зеркал ; самое простое - по вертикали. ; просто перенос 1 строчки в 8, 2 в 7, 3 в 6, и так далее For y = 0 To 31 Step 4 ; мотаем 8 строк For px = 0 To 3 ; по 4 байта в строчке UsedTiles(arrayitemnum)\ymirr[px + 28 - y] = UsedTiles(arrayitemnum)\original[px + y] Next Next ; по горизонтали. ; каждый байт надо переносить и докучи еще переворачивать внутри ; $12, $34, $56, $78 в $87, $65, $43, $21 ; используются сдвиги >> и маска & $F For y = 0 To 31 Step 4 ; мотаем 8 строк For px = 0 To 3 ; мотаем 4 байта, то есть 8 пикселей или 8 столбиков UsedTiles(arrayitemnum)\xmirr[y + px] = UsedTiles(arrayitemnum)\original[y + 3 - px] >> 4 UsedTiles(arrayitemnum)\xmirr[y + px] + (UsedTiles(arrayitemnum)\original[y + 3 - px] & $F) << 4 Next Next ; оба зеркала ; надо повторить зеркалирование по вертикали, ; просто вместо original указать xmirr - уже отзеркаленное по Х в качестве входных данных For y = 0 To 31 Step 4 ; мотаем 8 строк For px = 0 To 3 ; по 4 байта в строчке UsedTiles(arrayitemnum)\bothmirr[px + 28 - y] = UsedTiles(arrayitemnum)\xmirr[px + y] Next Next EndProcedure UsedTiles(0)\original[0] = $1 ;{ заполняем наш "тайл" ерундой от 1 до 32 UsedTiles(0)\original[1] = $2 UsedTiles(0)\original[2] = $3 UsedTiles(0)\original[3] = $4 UsedTiles(0)\original[4] = $5 UsedTiles(0)\original[5] = $6 UsedTiles(0)\original[6] = $7 UsedTiles(0)\original[7] = $8 UsedTiles(0)\original[8] = $9 UsedTiles(0)\original[9] = $10 UsedTiles(0)\original[10] = $11 UsedTiles(0)\original[11] = $12 UsedTiles(0)\original[12] = $13 UsedTiles(0)\original[13] = $14 UsedTiles(0)\original[14] = $15 UsedTiles(0)\original[15] = $16 UsedTiles(0)\original[16] = $17 UsedTiles(0)\original[17] = $18 UsedTiles(0)\original[18] = $19 UsedTiles(0)\original[19] = $20 UsedTiles(0)\original[20] = $21 UsedTiles(0)\original[21] = $22 UsedTiles(0)\original[22] = $23 UsedTiles(0)\original[23] = $24 UsedTiles(0)\original[24] = $25 UsedTiles(0)\original[25] = $26 UsedTiles(0)\original[26] = $27 UsedTiles(0)\original[27] = $28 UsedTiles(0)\original[28] = $29 UsedTiles(0)\original[29] = $30 UsedTiles(0)\original[30] = $31 UsedTiles(0)\original[31] = $32 ;} Debug "original:" For i = 0 To 31 Step 4 Debug RSet(Hex(UsedTiles(0)\original[i]), 2, "0") + " " + RSet(Hex(UsedTiles(0)\original[i + 1]), 2, "0") + " " + RSet(Hex(UsedTiles(0)\original[i + 2]), 2, "0") + " " + RSet(Hex(UsedTiles(0)\original[i + 3]), 2, "0") Next CreateMirrors(arrayitemnum.u) Debug "" Debug "ymirr:" For i = 0 To 31 Step 4 Debug RSet(Hex(UsedTiles(0)\ymirr[i]), 2, "0") + " " + RSet(Hex(UsedTiles(0)\ymirr[i + 1]), 2, "0") + " " + RSet(Hex(UsedTiles(0)\ymirr[i + 2]), 2, "0") + " " + RSet(Hex(UsedTiles(0)\ymirr[i + 3]), 2, "0") Next Debug "" Debug "xmirr:" For i = 0 To 31 Step 4 Debug RSet(Hex(UsedTiles(0)\xmirr[i]), 2, "0") + " " + RSet(Hex(UsedTiles(0)\xmirr[i + 1]), 2, "0") + " " + RSet(Hex(UsedTiles(0)\xmirr[i + 2]), 2, "0") + " " + RSet(Hex(UsedTiles(0)\xmirr[i + 3]), 2, "0") Next Debug "" Debug "both:" For i = 0 To 31 Step 4 Debug RSet(Hex(UsedTiles(0)\bothmirr[i]), 2, "0") + " " + RSet(Hex(UsedTiles(0)\bothmirr[i + 1]), 2, "0") + " " + RSet(Hex(UsedTiles(0)\bothmirr[i + 2]), 2, "0") + " " + RSet(Hex(UsedTiles(0)\bothmirr[i + 3]), 2, "0") Next --- Конец кода --- Основной же код будет чуть позжее... надо допиливать :blush: пока-что работает не так как надо... Так-же наверное сразу докручу сжатие. Хотя над сжатием еще бы покумекать. Основную идею то я понял... типа читает по 2 байта. И если вдруг 3 раза подряд это будет одно и то-же значение, то это дело можно поджать. Было: "12 34 12 34 12 34" Стало: "80 03 12 34" Типа первый бит в 80 - означает сжатие, дальше 003 - что три раза повторяются следующие 2 байта. *** В очередной раз, пока спал - думал. Надо переосмыслить подход. Плясать не от тайлового сета, а от тайловой карты. Читать номер тайла, переворачивать его в нужные зеркала, согласно инфе из карты, искать совпадения в уже внесенных тайлах в том моем сложном массиве и вносить в массив, если не было. А если было - просто менять старый номер на новый и править зеркала, если там были зеркала. Как бы с одной стороны куча ненужной работы в виде временного переворачивания тайла из тайлового сета... с другой стороны это делается средствами винды и современным компьютером, то как бы и ср@ть все равно ведь будет быстро. Внутренний перфекционист конечно сейчас бунтует... В выкидывателе тайлов я делал по другому. Можно конечно взять все оттуда, но хочу переосмыслить. |
| SeregaZ:
Сжатие, что описывал выше, будет примерно выглядеть так: --- Код: ---Enumeration #File EndEnumeration ; RLE word compressor (16bit). PureBasic. ; вход: *src указывает на необработанные данные (tile set), size - размер в байтах ; выход: *dst - сжатые данные ; PureBasic RLE-word encoder with big endian output for Sega MD Procedure WriteBEWord(*dst, pos, value) ; write 16bit big endian PokeA(*dst + pos, value >> 8) PokeA(*dst + pos + 1, value & $FF) EndProcedure Procedure ReadBEWord(*src, pos) ; Read 16-bit big-endian word from buffer (*src + pos) high = PeekA(*src + pos) low = PeekA(*src + pos + 1) ProcedureReturn (high * 256) + low EndProcedure Procedure RLEWord_Encode(*src, size, *dst) i = 0 o = 0 ; first 2 bytes = decompressed size in bytes WriteBEWord(*dst, o, size) : o + 2 While i < size ; Check for RLE run w = ReadBEWord(*src, i) count = 1 j = i + 2 ; Count consecutive identical words While j < size And ReadBEWord(*src, j) = w And count < $7FFF count + 1 j + 2 Wend If count >= 3 ; RLE encode if 3+ identical words WriteBEWord(*dst, o, $8000 | count) : o + 2 WriteBEWord(*dst, o, w) : o + 2 i = j Else ; RAW block - collect until we find 3+ identical words count = 1 j = i + 2 While j < size And count < $7FFF ; Check if we have 3 identical words ahead for RLE If j + 4 < size w1 = ReadBEWord(*src, j) w2 = ReadBEWord(*src, j + 2) w3 = ReadBEWord(*src, j + 4) If w1 = w2 And w1 = w3 Break ; Stop RAW block before RLE run EndIf EndIf count + 1 j + 2 Wend WriteBEWord(*dst, o, count) : o + 2 ; Copy raw words For k = 0 To count - 1 WriteBEWord(*dst, o, ReadBEWord(*src, i + k * 2)) : o + 2 Next i + count * 2 EndIf Wend ; End marker ;WriteBEWord(*dst, o, 0) : o + 2 ProcedureReturn o EndProcedure ; Main program error = 0 If ReadFile(#File, "tset.bin") ; получаем размер открытого файла tsetsize = Lof(#File) If tsetsize ; если размер больше 0 байт ; резервируем память нужного размера tsetmemory = AllocateMemory(tsetsize) tsetpakmem = AllocateMemory(tsetsize * 2) ; Может быть больше оригинала If tsetmemory And tsetpakmem ; если память зарезервировалась ; читаем из открытого файла в память ReadData(#File, tsetmemory, tsetsize) Else error = 1 EndIf Else ; добавляем текст ошибки, на случай если файл тайл сета 0 байт - пустой MessageRequester("ошибка", "файл тайл сета по всей видимости пуст - 0 байт") error = 1 EndIf CloseFile(#File) Else ; добавляем текст ошибки, на случай если файла тайл сета нет MessageRequester("ошибка", "нет файла тайл сета") error = 1 EndIf If error = 0 Debug "Original size: " + Str(tsetsize) paksize = RLEWord_Encode(tsetmemory, tsetsize, tsetpakmem) Debug "Compressed size: " + Str(paksize) If CreateFile(#File, "tsetP.bin") WriteData(#File, tsetpakmem, paksize) CloseFile(#File) ;MessageRequester("Успех", "Сжатие завершено!") Else MessageRequester("Ошибка", "Не могу создать файл tsetP.bin") EndIf FreeMemory(tsetmemory) FreeMemory(tsetpakmem) EndIf --- Конец кода --- Но смысла в этом не много... ибо если делать какие-то экраны для ромхаков, то ведь в разных играх по разному сжатие работает. Мне то для мув мейкера пойдет. А вот для ромхаков не очень... Так-же наверное надо сделать галку - нужно сжатие или не нужно. Так-же в самом асм коде надо будет добавить вилку - со сжатием тайловый сет или без и соответственно чтоб собиралось в ром или со сжатием или без, в зависимости от выбора. Еще правда с первыми двумя байтами - указанием размера разжатых данных - тоже надо покумекать... может оно и не нужно вовсе. С другой стороны - дополнительная проверка, а то ли мы разжали? Своеобразная контрольная сумма. |
| SeregaZ:
Добавил галку дизеринга, так-же раздуплил как нулевым номером в палитре заливать черный, независимо от того есть ли он на изображении или нет. Хотя еще надо потестить. Пока-что результаты с Железным Человеком такие: https://i126.fastpic.org/big/2025/1113/6f/fc3053626229f26032e81789ef19c86f.png Мой код из коробки, рожает по весу пикселей - жрет красный цвет накорню. С галкой дизеринга результат чуть лучше (понятно что одинаковых тайлов будет меньше, а изображение займет больше места в роме). Но палент квант, от которого я писаю кипятком, убил :) а контрольным в голову стал 4 палитровый вариант. Правда код импорта с 4 палитрами в сеговские цвета я недопилил, посему последнее изображение "сырое", но результат будет очень близкий. Наверное сделаю галки для импорта - в 1 палитру писать или в 4. Но если в 4, то тогда из палеткванта нужно будет сохранять два изображения - саму подготовленную картинку и палитру к ней. Просьба не размещать с помощью тэга img изображение со стороной более 700 пикселей. Предупреждение №2. ghostdog3 |
| Навигация |
| Главная страница сообщений |
| Следующая страница |
| Предыдущая страница |