TFT экран. Подвижный элемент на сложном фоне

Имеется TFT дисплей 240х320 65к на ILI9341. Надо двигать небольшой элемент по сложному фону. Фон с надписями и графическими элементами. В память, он понятное дело, не помещается, чтобы его частями перерисовать. Я могу в точности перерисовать фон, а потом сверху нарисовать подвижный элемент, но на время перерисовки подвижный элемент исчезает. Если бы фон был черным, то я бы просто закрашивал старый элемент и рисовал в новом месте Наверняка, во времена Агатов подобная проблема решалась. Подскажите алгоритм. Или по каким словам искать.

только хранить фон на SD, читать оттуда нужный кусок, рисовать над ним движущийся элемент и пхать в экран

1 лайк

хромакей chromakey

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

Я не знаю, что такое хромокей, который предложил @ЕвгенийП , но подозреваю что это тоже самое :slight_smile:

2 лайка

Как-то можно считать этот буфер с дисплея? Или мне надо математически высчитать. Ситуация такая, что у меня 7 основных фрагментов, на них надписи плюс вспомогательные линии.

Ирония в том, что я 3 дня рисовал векторные картинки, чтобы уйти от SD. Пока придержу как запасной вариант.

Зависит от дисплея и библиотеки. Чаще нет, чем да. Обычно на дисплей можно только писать, читать нельзя.

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

Спасибо всем ответившим. Надеялся на существование волшебного алгоритма :slight_smile:

И правильно надеялись. Я Вам даже сказал его название. Вы “не читатель”? Или гуглить в лом, ждёте, что кто-то на тарелочке принесёт вместе с готовой библиотекой? Впрочем, дело Ваше.

У агатов было 16 цветов в разрешении 128*128. 8Кбайт памяти на экранный буфер. Из 96Кбайт на борту.

Для твоего дисплея надо 300Кбайт памяти, а работаешь ты в лучшем случае на каком-то инвалидстве вроде меги, с 8Кбайт на борту.

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

1 лайк

Почему? Я посмотрел кучу фото на зеленом фоне.

Вы форумом ошиблись.

Ну, вот я говорю, что алгоритм Вы не искать не стали, ограничились просмотром фото (хоть красотки на фото-то?) :slight_smile:

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

с учетом того, что он не имеет прямого доступа к картинке и даже не умеет генерить ее по частям, а только всю целиком - не вижу, чем ему поможет хромокей

Единственное универсальное решение - экранный буфер - подразумевает, что объем оперативной памяти заметно превосходит объем видеопамяти.
В случае микроконтроллеров это, как правило, не так.
Поэтому первое подозрение, что Вы пытаетесь использовать свое “железо” не по назначению.

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

А Вы уверены, что “во времена Агатов” подобная проблема вообще была? У Агата оперативная память была меньше видеопамяти?

Я уже написал: универсального алгоритма не существует.
В отдельных частных случаях алгоритм можно придумать, но Вы не написали никаких подробностей, а потому какой именно у Вас частный случай - никто не знает.
Ну, например, одно дело, когда объект на экране может быть сдвинут не более, чем на 1 пиксель, и другое - когда объект может появиться в любом месте экрана.
Опять же, неизвестно, как соотносится размер спрайта с объемом видеопамяти…

Может, от SD надо было уходить по-другому: например, ESP-PSRAM64H - это 8 Мб внешней памяти. В ней вполне можно хранить несколько экранных буферов, а доступ к ней намного удобней и быстрей, чем к SD.

1 лайк

Это принципиальная разница.

Не знаю, не застал.

Ничего проще спрайтов не придумали, возьмите, как выше советовали, пожирнее МК, и будет вам счастье.
Вот вам пример, на STM32 было сделано, спрайт летает по экрану, динамически генерируется в памяти область вывода, т е берется кусок фона, на него накладывается изображение под нужным углом (8 направлений из первичной картинки спрайта генерируется) и еще в нескольких шагов (птичка крылышками машет). Потом уже эта область выводиться на экран.

Спойлер
/*
 * sprtft.cpp
 *
 *  Created on: 12 июл. 2022 г.
 *      Author: admin
 */

#include <sprtft.h>
#include <stdlib.h>
#include <string.h>

#include <f3spitft.h>
extern f3spitft tft;

sprtft::sprtft(unsigned short xSize, unsigned short ySize, unsigned char defDir) {
	// TODO Auto-generated constructor stub
	this->sizeX = xSize;
	this->sizeY = ySize;
	this->defaultDir = defDir & 0x07;
	this->currentDir = this->defaultDir;
	this->pointerMemBuffer = (unsigned char *) malloc(this->sizeX*this->sizeY*2);
	if (this->pointerMemBuffer) {
		this->countPics = 0;
		this->currentPic = 0;
		this->backgroundColor = 0x0000;
		this->ready = 1;
	} else {
		this->ready = 0;
	}
	this->isShow = 0;
	this->picBackground = NULL;
}

sprtft::~sprtft() {
	// TODO Auto-generated destructor stub
	if (this->pointerMemBuffer) free(this->pointerMemBuffer);
	this->ready = 0;
}

void sprtft::TFTaddPicSprite(unsigned char * inPic) {
	if (this->countPics < _tft_max_pics_in_sprite) {
		this->picsPointer[this->countPics] = inPic;
		++(this->countPics);
	}
}

void sprtft::TFTshowSprite(unsigned short xPos, unsigned short yPos, unsigned char inStep, unsigned char currDir) {
	if ((this->ready) && ((xPos + this->sizeX - 1) < tft.tftWIDTH) &&
			((yPos + this->sizeY - 1) < tft.tftHEIGHT) && (this->picBackground)) {
		this->posX = xPos;
		this->posY = yPos;
		this->currentPic = inStep;
		unsigned char newDir = currDir & 0x07;
		if (newDir != this->defaultDir) {
			this->rotationBufPic(newDir);
		} else {
			memcpy((unsigned char *)this->pointerMemBuffer, (unsigned char *)this->picsPointer[this->currentPic], this->sizeX * this->sizeY * 2);
		}
		unsigned short * outPic = (unsigned short *) this->pointerMemBuffer;
		unsigned short * bgrPic = (unsigned short *) this->picBackground + this->posX + this->posY * tft.tftWIDTH;
		for (unsigned short jy = 0; jy < this->sizeY; ++jy) { // lines
			unsigned short * lineOut = outPic + jy * this->sizeX;
			unsigned short * lineBgr = bgrPic + jy * tft.tftWIDTH;
			for (unsigned char ix = 0; ix < this->sizeX; ++ix) { // rows
				if ((*(lineOut+ix)) == this->backgroundColor) {
					*(lineOut+ix) = *(lineBgr+ix);
				}
			}
		}
		tft.tftPrintBuffer(this->posX, this->posY, this->posX + this->sizeX - 1, this->posY + this->sizeY - 1, this->pointerMemBuffer);
		while (tft.spiBUSY());
		this->isShow = 1;
	}
}

void sprtft::setBackground(unsigned char * inPic) {
	this->picBackground = inPic;
}

void sprtft::TFTclearSpriteBG(void) {
	if (this->isShow) {
		for (unsigned short jy = 0; jy < this->sizeY; ++jy) { // lines
			unsigned short * bgrPic = (unsigned short *) this->picBackground + this->posX + (this->posY + jy) * tft.tftWIDTH;
			unsigned short * outPic = (unsigned short *) this->pointerMemBuffer + jy * this->sizeX;
			memcpy((unsigned char *)outPic, (unsigned char *)bgrPic, this->sizeX * 2);
		}
		tft.tftPrintBuffer(this->posX, this->posY, this->posX + this->sizeX - 1, this->posY + this->sizeY - 1, this->pointerMemBuffer);
		while (tft.spiBUSY());
		this->isShow = 0;
	}
}

void sprtft::TFTmoveSprite(signed char moveX, signed char moveY, unsigned char changePic, unsigned char allowRotation) {
	unsigned short newX = this->posX + moveX;
	unsigned short newY = this->posY + moveY;
	if ((this->ready) && ((newX + this->sizeX - 1) < tft.tftWIDTH) &&
				((newY + this->sizeY - 1) < tft.tftHEIGHT) && (this->picBackground)) {
		this->TFTmoveClear(moveX, moveY); // очистка только части фона которая освобождается после движения
		//this->TFTclearSpriteBG(); // очистка всего места спрайта
		unsigned char newRotation;
		if (allowRotation) { // рассчитываем и меняем наклон картинки
			signed short divXY = (moveX*100) / moveY;
			if (moveX == 0) { // вверх или вних
				if (moveY < 0) newRotation = 0; else newRotation = 4;
			} else if (moveY == 0) { // влево или вправо
				if (moveX < 0) newRotation = 6; else newRotation = 2;
			} else if ((moveY < 0) && (moveX < 0)) { // левое верхнее направление
				if (divXY > 200) newRotation = 6;
				else if (divXY < 66) newRotation = 0;
				else newRotation = 7;
			} else if ((moveY >= 0) && (moveX >= 0)) { // правое нижнее направление
				if (divXY > 200) newRotation = 2;
				else if (divXY < 66) newRotation = 4;
				else newRotation = 3;
			} else if (moveY < 0) { // правое верхнее направление
				if (divXY > -66) newRotation = 0;
				else if (divXY < -200) newRotation = 2;
				else newRotation = 1;
			} else { // левое нижнее направление
				if (divXY > -66) newRotation = 4;
				else if (divXY < -200) newRotation = 6;
				else newRotation = 5;
			}
			// конец расчета направления
		} else { // оставляем текущее направление/наклон
			newRotation = this->currentDir;
		}
		if (changePic) { // меняем картинки
			unsigned char newStep = currentPic + 1;
			if (newStep >= this->countPics) newStep = 0;
			this->TFTshowSprite(newX, newY, newStep, newRotation);
		} else { // оставляем текущую картинку
			this->TFTshowSprite(newX, newY, this->currentPic, newRotation);
		}
	}
}

void sprtft::rotationBufPic(unsigned char nD) {
	if ((nD==0) || (nD==2) || (nD==4) || (nD==6)) { // поворот на угол кратный 90 гралусов
		if (nD>this->defaultDir) { // поворот по часовой стрелки
			if ((nD-this->defaultDir)==4) { // поворот на 180 градусов
				if (nD==6) { // зеркалирование по вертикальной оси
					this->mirrorBufPicVline();
				} else { // зеркалирование по горизонтальной оси
					this->mirrorBufPicHline();
				}
			} else if ((nD-this->defaultDir)==2) { // поворот на 90 градусов
				this->rotationBufPlus90Pic();
			} else { // поворот на 270 градусов
				this->rotationBufMinus90Pic();
			}
		} else { // поворот против часовой стрелки
			if ((this->defaultDir-nD)==4) { // поворот на 180 градусов
				if (nD==2) { // зеркалирование по вертикальной оси
					this->mirrorBufPicVline();
				} else { // зеркалирование по горизонтальной оси
					this->mirrorBufPicHline();
				}
			} else if ((this->defaultDir-nD)==2) { // поворот на 90 градусов
				this->rotationBufMinus90Pic();
			} else { // поворот на 270 градусов
				this->rotationBufPlus90Pic();
			}
		}
	} else if ((nD==1) || (nD==3) || (nD==5) || (nD==7)) { // повороты на углы кратные 45 градусов
		if (this->sizeX != this->sizeY) { // не квадрат не сможем повернуть
			memcpy((unsigned char *)this->pointerMemBuffer, (unsigned char *)this->picsPointer[this->currentPic], this->sizeX * this->sizeY * 2);
		} else { // квадрат - переворачиваем
			memset((unsigned char *)this->pointerMemBuffer, (unsigned char)(this->backgroundColor & 0xFF), this->sizeX * this->sizeY * 2); // чистим выходной буфер, что бы не обрабатывать пустые участки
			this->rotationBufBy45gPic(nD);
		}
	}
	this->currentDir = nD;
}

void sprtft::mirrorBufPicHline(void) {
	memcpy((unsigned char *)this->pointerMemBuffer, (unsigned char *)this->picsPointer[this->currentPic], this->sizeX * this->sizeY * 2);
	for (unsigned char iX=0; iX<this->sizeX; ++iX) {
		unsigned short * startRow = ((unsigned short *)this->pointerMemBuffer)+iX;
		for (unsigned char jY=0; jY<(this->sizeY/2); ++jY) {
			unsigned short * pix1 = startRow + jY*this->sizeX;
			unsigned short * pix2 = startRow + (this->sizeY-jY-1)*this->sizeX;
			unsigned short movePix = *pix1;
			*pix1 = *pix2;
			*pix2 = movePix;
		}
	}
}

void sprtft::mirrorBufPicVline(void) {
	memcpy((unsigned char *)this->pointerMemBuffer, (unsigned char *)this->picsPointer[this->currentPic], this->sizeX * this->sizeY * 2);
	for (unsigned char jY=0; jY<sizeY; ++jY) {
		unsigned short * startLine = ((unsigned short *)this->pointerMemBuffer)+jY*this->sizeX;
		for (unsigned char iX=0; iX<(this->sizeX/2); ++iX) {
			unsigned short * pix1 = startLine+iX;
			unsigned short * pix2 = startLine+(sizeY-iX-1);
			unsigned short movePix = *pix1;
			*pix1 = *pix2;
			*pix2 = movePix;
		}
	}
}

void sprtft::rotationBufPlus90Pic(void) {
	if (this->sizeX != this->sizeY) { // не квадрат не сможем повернуть
		memcpy((unsigned char *)this->pointerMemBuffer, (unsigned char *)this->picsPointer[this->currentPic], this->sizeX * this->sizeY * 2);
		return;
	}
	unsigned short * origPic = (unsigned short *)this->picsPointer[this->currentPic];
	unsigned short * destPic = (unsigned short *)this->pointerMemBuffer;
	for (unsigned char iX=0; iX<this->sizeX; ++iX) {
		for (unsigned char jY=0; jY<this->sizeY; ++jY) {
			unsigned short * pix1 = origPic + iX + (this->sizeX - jY - 1)*this->sizeX;
			unsigned short * pix2 = destPic + jY + (this->sizeY * iX);
			*pix2 = *pix1;
		}
	}
}

void sprtft::rotationBufMinus90Pic(void) {
	if (this->sizeX != this->sizeY) { // не квадрат не сможем повернуть
		memcpy((unsigned char *)this->pointerMemBuffer, (unsigned char *)this->picsPointer[this->currentPic], this->sizeX * this->sizeY * 2);
		return;
	}
	unsigned short * origPic = (unsigned short *)this->picsPointer[this->currentPic];
	unsigned short * destPic = (unsigned short *)this->pointerMemBuffer;
	for (unsigned char iX=0; iX<this->sizeX; ++iX) {
		for (unsigned char jY=0; jY<this->sizeY; ++jY) {
			unsigned short * pix1 = origPic + (this->sizeY - iX - 1) + (this->sizeX - jY - 1)*this->sizeX;
			unsigned short * pix2 = destPic + this->sizeX - jY - 1 + (this->sizeY * iX);
			*pix2 = *pix1;
		}
	}
}

void sprtft::rotationBufBy45gPic(unsigned char nD) { // поворотк картинки на угол кратно 45 градусов
	// целочисленной математикой
	signed long koefSC45g = 71L; // вычисленный коэфф для 45 градусов
	// начало массива спрайта
	unsigned short * origPic = (unsigned short *)this->picsPointer[this->currentPic];
	// начало массива куда выводить
	unsigned short * destPic = (unsigned short *)this->pointerMemBuffer;
	// цикл по всем пикселям картинки для поиска отображаемого
	for (unsigned char iX=0; iX<this->sizeX; ++iX) {
		for (unsigned char jY=0; jY<this->sizeY; ++jY) {
			// в зависимости от угла поворота, больше или меньше 45 градусов, выбираем разные пискесли с оригинальной картинки спрайта
			unsigned short * origPix;
			switch (nD) {
				case 1: {
					switch (this->defaultDir) { // выборка по положению направления начальной картинки
						case 0: {
							origPix = origPic + (this->sizeX - jY -1) + iX * this->sizeX;
							break;
						}
						case 2: {
							origPix = origPic + (this->sizeX - jY -1) + iX * this->sizeX;
							break;
						}
						case 4: {
							origPix = origPic + (this->sizeX - jY -1) + iX * this->sizeX;
							break;
						}
						case 6: {
							origPix = origPic + (this->sizeX - jY -1) + iX * this->sizeX;
							break;
						}
						default:{}
					}
					break;
				}
				case 3: {
					switch (this->defaultDir) { // выборка по положению направления начальной картинки
						case 0: {
							origPix = origPic + iX + jY * this->sizeX;
							break;
						}
						case 2: {
							origPix = origPic + iX + jY * this->sizeX;
							break;
						}
						case 4: {
							origPix = origPic + iX + jY * this->sizeX;
							break;
						}
						case 6: {
							origPix = origPic + iX + jY * this->sizeX;
							break;
						}
						default:{}
					}
					break;
				}
				case 5: {
					switch (this->defaultDir) { // выборка по положению направления начальной картинки
						case 0: {
							origPix = origPic + iX * this->sizeX + jY;
							break;
						}
						case 2: {
							origPix = origPic + iX * this->sizeX + jY;
							break;
						}
						case 4: {
							origPix = origPic + iX * this->sizeX + jY;
							break;
						}
						case 6: {
							origPix = origPic + iX * this->sizeX + jY;
							break;
						}
						default:{}
					}
					break;
				}
				case 7: {
					switch (this->defaultDir) { // выборка по положению направления начальной картинки
						case 0: {
							origPix = origPic + (this->sizeX - iX -1) + jY * this->sizeX;
							break;
						}
						case 2: {
							origPix = origPic + (this->sizeX - iX -1) + jY * this->sizeX;
							break;
						}
						case 4: {
							origPix = origPic + (this->sizeX - iX -1) + jY * this->sizeX;
							break;
						}
						case 6: {
							origPix = origPic + (this->sizeX - iX -1) + jY * this->sizeX;
							break;
						}
						default:{}
					}
					break;
				}
				default:{}
			}
			// ---------------
			unsigned short pixColor = *origPix; // цвет пикселя
			if (pixColor != this->backgroundColor) { // цвет не фон - работаем
				// получаем знаковые координаты для математики
				signed long sourX = (signed long)iX - ((signed long)(this->sizeX/2));
				signed long sourY = (signed long)jY - ((signed long)(this->sizeY/2));
				signed long KX = sourX * koefSC45g;
				signed long KY = sourY * koefSC45g;
				signed long destX = KX-KY;
				signed long destY = KX+KY;
				// поскольку при повороте по алгоритму много прозрачных точек получается
				// то сохраняем координаты для вывода еще точки рядом
				signed long repX = destX;
				signed long repY = destY;
				// округляем к ближайшему
				destX += 50;
				destY += 50;
				// приводим к реальным цифрам
				destX /=100;
				destY /=100;
				// преобразуем знаковые кординаты обратно в координаты массива
				destX += this->sizeX/2;
				destY += this->sizeY/2;
				// проверка выхода за границу квадрата
				if ((destX >= 0) && (destX < this->sizeX) &&
						(destY >= 0) && (destY < this->sizeY)) {
					// выходной адрес памяти
					unsigned short * destPix = destPic + destY * this->sizeX + destX;
					// выводим пиксель
					*destPix = pixColor;
					// повторяем еще один смещенный символ
					// приводим к реальным цифрам
					repX /=100;
					repY /=100;
					// преобразуем знаковые кординаты обратно в координаты массива
					repX += this->sizeX/2;
					repY += this->sizeY/2;
					// проверка выхода за границу квадрата
					if ((repX >= 0) && (repX < this->sizeX) &&
							(repY >= 0) && (repY < this->sizeY)) {
						// выходной адрес памяти
						unsigned short * destPix = destPic + repY * this->sizeX + repX;
						// выводим пиксель
						*destPix = pixColor;
					}
				}
			}
		}
	}
}

void sprtft::TFTmoveClear(signed char moveX, signed char moveY) {
	if (moveX == 0) { // движение только по вертикали
		if (moveY < 0) { // вверх
		} else { // вниз
			for (unsigned short jy = 0; jy < moveY; ++jy) { // lines
				unsigned short * bgrPic = (unsigned short *) this->picBackground + this->posX + (this->posY + jy) * tft.tftWIDTH;
				unsigned short * outPic = (unsigned short *) this->pointerMemBuffer + jy * this->sizeX;
				memcpy((unsigned char *)outPic, (unsigned char *)bgrPic, this->sizeX * 2);
			}
			tft.tftPrintBuffer(this->posX, this->posY, this->posX + this->sizeX - 1, this->posY + this->sizeY - 1, this->pointerMemBuffer);
			while (tft.spiBUSY());
		}
	} else if (moveY == 0) { // движение только по горизонтали
		if (moveX < 0) { // влево
		} else { // направо
		}
	}
	this->isShow = 0;
}

Наверняка аппаратная возможность есть под 9341. И это решение задачи про УНО и 153 Кб на картинку в экран. Но готовых функций в рамках библиотек нет. Один на этом форуме для ST7735 писал библиотеку и даже как я понял аппаратные изменения делал, но его раскритиковали…
…А так я думаю тут несколько людей могут в рамках SPI.h функцию изобразить. Правда опять же ходят слухи (даташиты не читал), что внутри экранов на пиксель 18 бит, а не 16. Это нюансы делает :slight_smile:

153кБ плюс спрайт?

На esp32 пример из игрового эпизода.
VID_20230304_064812
VID_20230302_183811

1 лайк