Files
QTeletextMaker/src/qteletextdecoder/render.cpp

465 lines
16 KiB
C++
Raw Normal View History

2020-09-06 16:47:38 +01:00
/*
2024-12-31 10:51:06 +00:00
* Copyright (C) 2020-2025 Gavin MacGregor
2020-09-06 16:47:38 +01:00
*
* This file is part of QTeletextMaker.
*
* QTeletextMaker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* QTeletextMaker is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with QTeletextMaker. If not, see <https://www.gnu.org/licenses/>.
*/
#include <QBitmap>
#include <QColor>
#include <QDir>
#include <QImage>
2020-09-06 16:47:38 +01:00
#include <QPainter>
#include <QPixmap>
2020-09-06 16:47:38 +01:00
#include "render.h"
#include "decode.h"
2021-06-23 11:23:11 +01:00
int TeletextFontBitmap::s_instances = 0;
QBitmap *TeletextFontBitmap::s_fontBitmap = nullptr;
QImage *TeletextFontBitmap::s_fontImage = nullptr;
2021-06-23 11:23:11 +01:00
TeletextFontBitmap::TeletextFontBitmap()
2020-09-06 16:47:38 +01:00
{
Q_INIT_RESOURCE(teletextfonts);
if (s_instances == 0) {
s_fontBitmap = new QBitmap(":/fontimages/teletextfont.png");
s_fontImage = new QImage(s_fontBitmap->toImage());
}
2021-06-23 11:23:11 +01:00
s_instances++;
}
2020-09-06 16:47:38 +01:00
2021-06-23 11:23:11 +01:00
TeletextFontBitmap::~TeletextFontBitmap()
{
s_instances--;
if (s_instances == 0) {
delete s_fontImage;
delete s_fontBitmap;
}
2021-06-23 11:23:11 +01:00
}
2021-06-23 11:23:11 +01:00
TeletextPageRender::TeletextPageRender()
{
2020-09-06 16:47:38 +01:00
for (int i=0; i<6; i++)
m_pageImage[i] = new QImage(864, 250, QImage::Format_ARGB32_Premultiplied);
m_reveal = false;
m_renderMode = RenderNormal;
m_showControlCodes = false;
m_flashBuffersHz = 0;
for (int r=0; r<25; r++) {
m_flashingRow[r] = 0;
for (int c=0; c<40; c++)
m_controlCodeCache[r][c] = 0x7f;
}
2020-09-06 16:47:38 +01:00
}
TeletextPageRender::~TeletextPageRender()
{
for (int i=0; i<6; i++)
delete m_pageImage[i];
2020-09-06 16:47:38 +01:00
}
void TeletextPageRender::setDecoder(TeletextPageDecode *decoder)
2020-09-06 16:47:38 +01:00
{
m_decoder = decoder;
2020-09-06 16:47:38 +01:00
}
inline void TeletextPageRender::drawFromBitmap(QPainter &painter, int r, int c, const QImage image, TeletextPageDecode::CharacterFragment characterFragment)
{
switch (characterFragment) {
case TeletextPageDecode::NormalSize:
painter.drawImage(c*12, r*10, image);
break;
case TeletextPageDecode::DoubleHeightTopHalf:
painter.drawImage(QRect(c*12, r*10, 12, 10), image, QRect(0, 0, 12, 5));
break;
case TeletextPageDecode::DoubleHeightBottomHalf:
painter.drawImage(QRect(c*12, r*10, 12, 10), image, QRect(0, 5, 12, 5));
break;
case TeletextPageDecode::DoubleWidthLeftHalf:
painter.drawImage(QRect(c*12, r*10, 12, 10), image, QRect(0, 0, 6, 10));
break;
case TeletextPageDecode::DoubleWidthRightHalf:
painter.drawImage(QRect(c*12, r*10, 12, 10), image, QRect(6, 0, 6, 10));
break;
case TeletextPageDecode::DoubleSizeTopLeftQuarter:
painter.drawImage(QRect(c*12, r*10, 12, 10), image, QRect(0, 0, 6, 5));
break;
case TeletextPageDecode::DoubleSizeTopRightQuarter:
painter.drawImage(QRect(c*12, r*10, 12, 10), image, QRect(6, 0, 6, 5));
break;
case TeletextPageDecode::DoubleSizeBottomLeftQuarter:
painter.drawImage(QRect(c*12, r*10, 12, 10), image, QRect(0, 5, 6, 5));
break;
case TeletextPageDecode::DoubleSizeBottomRightQuarter:
painter.drawImage(QRect(c*12, r*10, 12, 10), image, QRect(6, 5, 6, 5));
break;
}
}
inline void TeletextPageRender::drawFromFontBitmap(QPainter &painter, int r, int c, unsigned char characterCode, int characterSet, TeletextPageDecode::CharacterFragment characterFragment)
2020-09-06 16:47:38 +01:00
{
switch (characterFragment) {
case TeletextPageDecode::NormalSize:
painter.drawImage(c*12, r*10, *m_fontBitmap.image(), (characterCode-32)*12, characterSet*10, 12, 10);
break;
case TeletextPageDecode::DoubleHeightTopHalf:
painter.drawImage(QRect(c*12, r*10, 12, 10), *m_fontBitmap.image(), QRect((characterCode-32)*12, characterSet*10, 12, 5));
break;
case TeletextPageDecode::DoubleHeightBottomHalf:
painter.drawImage(QRect(c*12, r*10, 12, 10), *m_fontBitmap.image(), QRect((characterCode-32)*12, characterSet*10+5, 12, 5));
break;
case TeletextPageDecode::DoubleWidthLeftHalf:
painter.drawImage(QRect(c*12, r*10, 12, 10), *m_fontBitmap.image(), QRect((characterCode-32)*12, characterSet*10, 6, 10));
break;
case TeletextPageDecode::DoubleWidthRightHalf:
painter.drawImage(QRect(c*12, r*10, 12, 10), *m_fontBitmap.image(), QRect((characterCode-32)*12+6, characterSet*10, 6, 10));
break;
case TeletextPageDecode::DoubleSizeTopLeftQuarter:
painter.drawImage(QRect(c*12, r*10, 12, 10), *m_fontBitmap.image(), QRect((characterCode-32)*12, characterSet*10, 6, 5));
break;
case TeletextPageDecode::DoubleSizeTopRightQuarter:
painter.drawImage(QRect(c*12, r*10, 12, 10), *m_fontBitmap.image(), QRect((characterCode-32)*12+6, characterSet*10, 6, 5));
break;
case TeletextPageDecode::DoubleSizeBottomLeftQuarter:
painter.drawImage(QRect(c*12, r*10, 12, 10), *m_fontBitmap.image(), QRect((characterCode-32)*12, characterSet*10+5, 6, 5));
break;
case TeletextPageDecode::DoubleSizeBottomRightQuarter:
painter.drawImage(QRect(c*12, r*10, 12, 10), *m_fontBitmap.image(), QRect((characterCode-32)*12+6, characterSet*10+5, 6, 5));
break;
}
2020-09-06 16:47:38 +01:00
}
inline void TeletextPageRender::drawCharacter(QPainter &painter, int r, int c, unsigned char characterCode, int characterSet, int characterDiacritical, TeletextPageDecode::CharacterFragment characterFragment)
2020-09-06 16:47:38 +01:00
{
const bool dontUnderline = characterCode == 0x00;
if (dontUnderline)
characterCode = 0x20;
if (characterCode == 0x20 && characterSet < 25 && characterDiacritical == 0)
painter.fillRect(c*12, r*10, 12, 10, m_backgroundQColor);
else if (characterCode == 0x7f && characterSet == 24)
painter.fillRect(c*12, r*10, 12, 10, m_foregroundQColor);
else if ((m_decoder->cellBold(r, c) || m_decoder->cellItalic(r, c)) && characterSet < 24)
drawBoldOrItalicCharacter(painter, r, c, characterCode, characterSet, characterFragment);
else {
2025-03-18 16:24:12 +00:00
m_fontBitmap.image()->setColorTable(QList<QRgb>{m_backgroundQColor.rgba(), m_foregroundQColor.rgba()});
drawFromFontBitmap(painter, r, c, characterCode, characterSet, characterFragment);
}
if (m_decoder->cellUnderlined(r, c) && !dontUnderline) {
painter.setPen(m_foregroundQColor);
switch (characterFragment) {
case TeletextPageDecode::NormalSize:
case TeletextPageDecode::DoubleWidthLeftHalf:
case TeletextPageDecode::DoubleWidthRightHalf:
painter.drawLine(c*12, r*10+9, c*12+11, r*10+9);
break;
case TeletextPageDecode::DoubleHeightBottomHalf:
case TeletextPageDecode::DoubleSizeBottomLeftQuarter:
case TeletextPageDecode::DoubleSizeBottomRightQuarter:
painter.drawRect(c*12, r*10+8, 11, 1);
break;
default:
break;
}
}
2020-09-06 16:47:38 +01:00
if (characterDiacritical != 0) {
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
2025-03-18 16:24:12 +00:00
m_fontBitmap.image()->setColorTable(QList<QRgb>{0x00000000, m_foregroundQColor.rgba()});
drawFromFontBitmap(painter, r, c, characterDiacritical+64, 7, characterFragment);
painter.setCompositionMode(QPainter::CompositionMode_Source);
2020-09-06 16:47:38 +01:00
}
}
inline void TeletextPageRender::drawBoldOrItalicCharacter(QPainter &painter, int r, int c, unsigned char characterCode, int characterSet, TeletextPageDecode::CharacterFragment characterFragment)
{
QImage styledImage = QImage(12, 10, QImage::Format_Mono);
QPainter styledPainter;
2025-03-18 16:24:12 +00:00
m_fontBitmap.image()->setColorTable(QList<QRgb>{m_backgroundQColor.rgba(), m_foregroundQColor.rgba()});
styledImage.setColorTable(QList<QRgb>{m_backgroundQColor.rgba(), m_foregroundQColor.rgba()});
if (m_decoder->cellItalic(r, c)) {
styledImage.fill(0);
styledPainter.begin(&styledImage);
styledPainter.setCompositionMode(QPainter::CompositionMode_Source);
styledPainter.drawImage(1, 0, *m_fontBitmap.image(), (characterCode-32)*12, characterSet*10, 11, 3);
styledPainter.drawImage(0, 3, *m_fontBitmap.image(), (characterCode-32)*12, characterSet*10+3, 12, 3);
styledPainter.drawImage(0, 6, *m_fontBitmap.image(), (characterCode-32)*12+1, characterSet*10+6, 11, 4);
styledPainter.end();
} else
styledImage = m_fontBitmap.image()->copy((characterCode-32)*12, characterSet*10, 12, 10);
if (m_decoder->cellBold(r, c)) {
QImage boldeningImage;
boldeningImage = styledImage.copy();
styledPainter.begin(&styledImage);
styledPainter.setCompositionMode(QPainter::CompositionMode_SourceOver);
2025-03-18 16:24:12 +00:00
boldeningImage.setColorTable(QList<QRgb>{0x00000000, m_foregroundQColor.rgba()});
styledPainter.drawImage(1, 0, boldeningImage);
styledPainter.end();
}
drawFromBitmap(painter, r, c, styledImage, characterFragment);
}
void TeletextPageRender::renderPage(bool force)
2020-09-06 16:47:38 +01:00
{
if (m_renderMode == RenderWhiteOnBlack) {
m_foregroundQColor = Qt::white;
m_backgroundQColor = Qt::black;
} else if (m_renderMode == RenderBlackOnWhite) {
m_foregroundQColor = Qt::black;
m_backgroundQColor = Qt::white;
}
for (int r=0; r<25; r++)
renderRow(r, 0, force);
}
2020-09-06 16:47:38 +01:00
void TeletextPageRender::renderRow(int r, int ph, bool force)
{
QPainter painter;
int flashingRow = 0;
bool rowRefreshed = false;
painter.begin(m_pageImage[ph]);
painter.setCompositionMode(QPainter::CompositionMode_Source);
for (int c=0; c<72; c++) {
bool controlCodeChanged = false;
// Ensure that shown control codes are refreshed
if (ph == 0 && m_showControlCodes && c < 40 && (m_controlCodeCache[r][c] != 0x7f || m_decoder->teletextPage()->character(r, c) < 0x20)) {
controlCodeChanged = m_controlCodeCache[r][c] != m_decoder->teletextPage()->character(r, c);
if (controlCodeChanged) {
if (m_decoder->teletextPage()->character(r, c) < 0x20)
m_controlCodeCache[r][c] = m_decoder->teletextPage()->character(r, c);
else
m_controlCodeCache[r][c] = 0x7f;
}
2020-09-06 16:47:38 +01:00
}
// Second part of "if" suppresses all flashing on monochrome render modes
if (ph == 0 && m_renderMode < RenderWhiteOnBlack) {
if (m_decoder->cellFlashMode(r, c) != 0)
flashingRow = qMax(flashingRow, (m_decoder->cellFlashRatePhase(r, c) == 0) ? 1 : 2);
// } else if (!force)
} else
force = m_decoder->cellFlashMode(r, c) != 0;
// If drawing into a flash pixmap buffer, "force" is set on a flashing cell only
// and since the refresh and controlCodeChanged variables will be false at this point
// only flashing cells will be drawn
if (m_decoder->refresh(r, c) || force || controlCodeChanged) {
2025-06-01 12:50:33 +01:00
bool flashPhOn = true; // Must remain "true" on non-flashing cell
const bool concealed = !m_reveal && m_decoder->cellConceal(r, c);
rowRefreshed = true;
if (m_renderMode < RenderWhiteOnBlack) {
if (m_decoder->cellFlashMode(r, c) == 0)
m_foregroundQColor = m_decoder->cellForegroundQColor(r, c);
else {
// Flashing cell, decide if phase in this cycle is on or off
if (m_decoder->cellFlashRatePhase(r, c) == 0)
2025-06-01 12:50:33 +01:00
flashPhOn = (ph < 3) ^ (m_decoder->cellFlashMode(r, c) == 2);
else
2025-06-01 12:50:33 +01:00
flashPhOn = ((ph == m_decoder->cellFlash2HzPhaseNumber(r, c)-1) || (ph == m_decoder->cellFlash2HzPhaseNumber(r, c)+2)) ^ (m_decoder->cellFlashMode(r, c) == 2);
// If flashing to adjacent CLUT select the appropriate foreground colour
2025-06-01 12:50:33 +01:00
if (m_decoder->cellFlashMode(r, c) == 3 && !flashPhOn)
m_foregroundQColor = m_decoder->cellFlashForegroundQColor(r, c);
else
m_foregroundQColor = m_decoder->cellForegroundQColor(r, c);
2020-09-06 16:47:38 +01:00
}
if (m_renderMode != RenderMix || m_decoder->cellBoxed(r, c))
m_backgroundQColor = m_decoder->cellBackgroundQColor(r, c);
else
m_backgroundQColor = Qt::transparent;
}
2020-09-06 16:47:38 +01:00
2025-06-01 12:50:33 +01:00
if (((m_decoder->cellFlashMode(r, c) == 1 || m_decoder->cellFlashMode(r, c) == 2) && !flashPhOn))
// If flashing mode is Normal or Invert, draw a space instead of a character on phase
// Character 0x00 draws space without underline
drawCharacter(painter, r, c, 0x00, 0, 0, m_decoder->cellCharacterFragment(r, c));
else if (concealed)
drawCharacter(painter, r, c, 0x20, 0, 0, m_decoder->cellCharacterFragment(r, c));
else
drawCharacter(painter, r, c, m_decoder->cellCharacterCode(r, c), m_decoder->cellCharacterSet(r, c), m_decoder->cellCharacterDiacritical(r, c), m_decoder->cellCharacterFragment(r, c));
2020-09-06 16:47:38 +01:00
if (m_showControlCodes && c < 40 && m_decoder->teletextPage()->character(r, c) < 0x20) {
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
2025-03-18 16:24:12 +00:00
m_fontBitmap.image()->setColorTable(QList<QRgb>{0x7f000000, 0xe0ffffff});
painter.drawImage(c*12, r*10, *m_fontBitmap.image(), (m_decoder->teletextPage()->character(r, c)+32)*12, 250, 12, 10);
painter.setCompositionMode(QPainter::CompositionMode_Source);
}
}
}
2020-09-06 16:47:38 +01:00
painter.end();
2020-09-06 16:47:38 +01:00
if (ph != 0)
return;
2020-09-06 16:47:38 +01:00
if (flashingRow == 3)
flashingRow = 2;
if (flashingRow != m_flashingRow[r])
setRowFlashStatus(r, flashingRow);
for (int c=0; c<72; c++)
m_decoder->setRefresh(r, c, false);
// If row had changes rendered and flashing is present anywhere on the screen,
// copy this rendered line into the other flash pixmap buffers and then re-render
// the flashing cells in those buffers
if (rowRefreshed && m_flashBuffersHz > 0) {
painter.begin(m_pageImage[3]);
painter.setCompositionMode(QPainter::CompositionMode_Source);
painter.drawImage(0, r*10, *m_pageImage[0], 0, r*10, 864, 10);
painter.end();
renderRow(r, 3);
2020-09-06 16:47:38 +01:00
if (m_flashBuffersHz == 2) {
painter.begin(m_pageImage[1]);
painter.setCompositionMode(QPainter::CompositionMode_Source);
painter.drawImage(0, r*10, *m_pageImage[0], 0, r*10, 864, 10);
painter.end();
painter.begin(m_pageImage[2]);
painter.setCompositionMode(QPainter::CompositionMode_Source);
painter.drawImage(0, r*10, *m_pageImage[0], 0, r*10, 864, 10);
painter.end();
painter.begin(m_pageImage[4]);
painter.setCompositionMode(QPainter::CompositionMode_Source);
painter.drawImage(0, r*10, *m_pageImage[3], 0, r*10, 864, 10);
painter.end();
painter.begin(m_pageImage[5]);
painter.setCompositionMode(QPainter::CompositionMode_Source);
painter.drawImage(0, r*10, *m_pageImage[3], 0, r*10, 864, 10);
painter.end();
renderRow(r, 1);
renderRow(r, 2);
renderRow(r, 4);
renderRow(r, 5);
2020-09-06 16:47:38 +01:00
}
}
}
void TeletextPageRender::setRowFlashStatus(int r, int rowFlashHz)
2020-09-06 16:47:38 +01:00
{
m_flashingRow[r] = rowFlashHz;
2020-09-06 16:47:38 +01:00
if (rowFlashHz == m_flashBuffersHz)
return;
2020-09-06 16:47:38 +01:00
if (rowFlashHz < m_flashBuffersHz) {
// New flash Hz for this row is lower than the entire screen flash Hz
// Check the other rows if they still need flashing at the current flash Hz
// If not, reduce the screen flash Hz
int highestRowFlashHz = rowFlashHz;
for (int ri=0; ri<25; ri++)
if (m_flashingRow[ri] > highestRowFlashHz) {
highestRowFlashHz = m_flashingRow[ri];
if (highestRowFlashHz == 2)
break;
}
if (highestRowFlashHz > rowFlashHz)
rowFlashHz = highestRowFlashHz;
if (rowFlashHz == m_flashBuffersHz)
return;
m_flashBuffersHz = rowFlashHz;
emit flashChanged(m_flashBuffersHz);
2020-09-06 16:47:38 +01:00
return;
}
2020-09-06 16:47:38 +01:00
// If we get here, new flash Hz for this row is higher than the entire flash Hz
// so prepare the pixmap flash buffers
if (m_flashBuffersHz == 0)
*m_pageImage[3] = m_pageImage[0]->copy();
if (rowFlashHz == 2) {
*m_pageImage[1] = m_pageImage[0]->copy();
*m_pageImage[2] = m_pageImage[0]->copy();
*m_pageImage[4] = m_pageImage[3]->copy();
*m_pageImage[5] = m_pageImage[3]->copy();
2020-09-06 16:47:38 +01:00
}
m_flashBuffersHz = rowFlashHz;
emit flashChanged(m_flashBuffersHz);
2020-09-06 16:47:38 +01:00
}
void TeletextPageRender::colourChanged(int index)
2020-09-06 16:47:38 +01:00
{
for (int r=0; r<25; r++)
for (int c=0; c<72; c++) {
if (m_decoder->cellForegroundCLUT(r, c) == index || m_decoder->cellBackgroundCLUT(r, c) == index || m_decoder->cellForegroundCLUT(r, c) == 8 || m_decoder->cellBackgroundCLUT(r, c) == 8)
m_decoder->setRefresh(r, c, true);
if (m_decoder->cellFlashMode(r, c) == 3 && ((m_decoder->cellForegroundCLUT(r, c) ^ 8) == index || (m_decoder->cellForegroundCLUT(r, c) ^ 8) == 8))
m_decoder->setRefresh(r, c, true);
}
2020-09-06 16:47:38 +01:00
}
void TeletextPageRender::setReveal(bool reveal)
2020-09-06 16:47:38 +01:00
{
if (reveal == m_reveal)
2020-09-06 16:47:38 +01:00
return;
2021-01-31 11:54:45 +00:00
m_reveal = reveal;
2020-09-06 16:47:38 +01:00
for (int r=0; r<25; r++)
for (int c=0; c<72; c++)
if (m_decoder->cellConceal(r, c))
m_decoder->setRefresh(r, c, true);
2020-09-06 16:47:38 +01:00
}
void TeletextPageRender::setRenderMode(RenderMode renderMode)
2020-09-06 16:47:38 +01:00
{
if (renderMode == m_renderMode)
return;
2020-09-06 16:47:38 +01:00
m_renderMode = renderMode;
2020-09-06 16:47:38 +01:00
for (int r=0; r<25; r++)
for (int c=0; c<72; c++)
m_decoder->setRefresh(r, c, true);
2020-09-06 16:47:38 +01:00
}
void TeletextPageRender::setShowControlCodes(bool showControlCodes)
2020-09-06 16:47:38 +01:00
{
if (showControlCodes == m_showControlCodes)
2020-09-06 16:47:38 +01:00
return;
m_showControlCodes = showControlCodes;
2020-09-06 16:47:38 +01:00
for (int r=0; r<25; r++)
for (int c=0; c<40; c++)
if (m_decoder->teletextPage()->character(r, c) < 0x20)
m_decoder->setRefresh(r, c, true);
2020-09-06 16:47:38 +01:00
}