Files
QTeletextMaker/render.cpp

492 lines
17 KiB
C++
Raw Normal View History

2020-09-06 16:47:38 +01:00
/*
2022-12-31 21:19:15 +00:00
* Copyright (C) 2020-2023 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>
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;
TeletextFontBitmap::TeletextFontBitmap()
2020-09-06 16:47:38 +01:00
{
2021-06-23 11:23:11 +01:00
if (s_instances == 0)
s_fontBitmap = new QBitmap(":/images/teletextfont.png");
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_fontBitmap;
}
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_pagePixmap[i] = new QPixmap(864, 250);
m_pagePixmap[0]->fill(Qt::transparent);
m_reveal = false;
m_mix = false;
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_pagePixmap[i];
}
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 &pixmapPainter, int r, int c, const QBitmap bitmap, TeletextPageDecode::CharacterFragment characterFragment)
{
switch (characterFragment) {
case TeletextPageDecode::NormalSize:
pixmapPainter.drawPixmap(c*12, r*10, bitmap);
break;
case TeletextPageDecode::DoubleHeightTopHalf:
pixmapPainter.drawPixmap(c*12, r*10, 12, 10, bitmap, 0, 0, 12, 5);
break;
case TeletextPageDecode::DoubleHeightBottomHalf:
pixmapPainter.drawPixmap(c*12, r*10, 12, 10, bitmap, 0, 5, 12, 5);
break;
case TeletextPageDecode::DoubleWidthLeftHalf:
pixmapPainter.drawPixmap(c*12, r*10, 12, 10, bitmap, 0, 0, 6, 10);
break;
case TeletextPageDecode::DoubleWidthRightHalf:
pixmapPainter.drawPixmap(c*12, r*10, 12, 10, bitmap, 6, 0, 6, 10);
break;
case TeletextPageDecode::DoubleSizeTopLeftQuarter:
pixmapPainter.drawPixmap(c*12, r*10, 12, 10, bitmap, 0, 0, 6, 5);
break;
case TeletextPageDecode::DoubleSizeTopRightQuarter:
pixmapPainter.drawPixmap(c*12, r*10, 12, 10, bitmap, 6, 0, 6, 5);
break;
case TeletextPageDecode::DoubleSizeBottomLeftQuarter:
pixmapPainter.drawPixmap(c*12, r*10, 12, 10, bitmap, 0, 5, 6, 5);
break;
case TeletextPageDecode::DoubleSizeBottomRightQuarter:
pixmapPainter.drawPixmap(c*12, r*10, 12, 10, bitmap, 6, 5, 6, 5);
break;
}
}
inline void TeletextPageRender::drawFromFontBitmap(QPainter &pixmapPainter, 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:
pixmapPainter.drawPixmap(c*12, r*10, *m_fontBitmap.rawBitmap(), (characterCode-32)*12, characterSet*10, 12, 10);
break;
case TeletextPageDecode::DoubleHeightTopHalf:
pixmapPainter.drawPixmap(c*12, r*10, 12, 10, *m_fontBitmap.rawBitmap(), (characterCode-32)*12, characterSet*10, 12, 5);
break;
case TeletextPageDecode::DoubleHeightBottomHalf:
pixmapPainter.drawPixmap(c*12, r*10, 12, 10, *m_fontBitmap.rawBitmap(), (characterCode-32)*12, characterSet*10+5, 12, 5);
break;
case TeletextPageDecode::DoubleWidthLeftHalf:
pixmapPainter.drawPixmap(c*12, r*10, 12, 10, *m_fontBitmap.rawBitmap(), (characterCode-32)*12, characterSet*10, 6, 10);
break;
case TeletextPageDecode::DoubleWidthRightHalf:
pixmapPainter.drawPixmap(c*12, r*10, 12, 10, *m_fontBitmap.rawBitmap(), (characterCode-32)*12+6, characterSet*10, 6, 10);
break;
case TeletextPageDecode::DoubleSizeTopLeftQuarter:
pixmapPainter.drawPixmap(c*12, r*10, 12, 10, *m_fontBitmap.rawBitmap(), (characterCode-32)*12, characterSet*10, 6, 5);
break;
case TeletextPageDecode::DoubleSizeTopRightQuarter:
pixmapPainter.drawPixmap(c*12, r*10, 12, 10, *m_fontBitmap.rawBitmap(), (characterCode-32)*12+6, characterSet*10, 6, 5);
break;
case TeletextPageDecode::DoubleSizeBottomLeftQuarter:
pixmapPainter.drawPixmap(c*12, r*10, 12, 10, *m_fontBitmap.rawBitmap(), (characterCode-32)*12, characterSet*10+5, 6, 5);
break;
case TeletextPageDecode::DoubleSizeBottomRightQuarter:
pixmapPainter.drawPixmap(c*12, r*10, 12, 10, *m_fontBitmap.rawBitmap(), (characterCode-32)*12+6, characterSet*10+5, 6, 5);
break;
}
2020-09-06 16:47:38 +01:00
}
inline void TeletextPageRender::drawCharacter(QPainter &pixmapPainter, 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 either foreground or background is set to transparent
// tinker with the QPainter settings so we get the desired result
if (!pixmapPainter.background().isOpaque()) {
if (pixmapPainter.pen().color().alpha() == 0) {
// Transparent foreground and background
pixmapPainter.setCompositionMode(QPainter::CompositionMode_Clear);
pixmapPainter.eraseRect(c*12, r*10, 12, 10);
pixmapPainter.setCompositionMode(QPainter::CompositionMode_SourceOver);
2020-09-06 16:47:38 +01:00
return;
} else
// Transparent background, opaque foreground
pixmapPainter.setCompositionMode(QPainter::CompositionMode_Source);
} else if (pixmapPainter.pen().color().alpha() == 0) {
// Transparent foreground, opaque background
// Deal with optimising G1 solid 7/F blocks and spaces now
// otherwise the same optimisations later on won't work with
// our tinkered QPainter settings
if (characterCode == 0x7f && characterSet == 24) {
pixmapPainter.setCompositionMode(QPainter::CompositionMode_Clear);
pixmapPainter.eraseRect(c*12, r*10, 12, 10);
pixmapPainter.setCompositionMode(QPainter::CompositionMode_SourceOver);
2020-09-06 16:47:38 +01:00
return;
}
2020-09-06 16:47:38 +01:00
pixmapPainter.fillRect(c*12, r*10, 12, 10, m_decoder->cellBackgroundQColor(r, c));
2020-09-06 16:47:38 +01:00
if (characterCode == 0x20 && characterSet < 25 && characterDiacritical == 0)
return;
pixmapPainter.setBackground(QColor(0, 0, 0, 0));
pixmapPainter.setPen(QColor(255, 255, 255, 255));
pixmapPainter.setCompositionMode(QPainter::CompositionMode_DestinationOut);
}
2020-09-06 16:47:38 +01:00
if (characterCode == 0x20 && characterSet < 25 && characterDiacritical == 0)
pixmapPainter.fillRect(c*12, r*10, 12, 10, pixmapPainter.background().color());
else if (characterCode == 0x7f && characterSet == 24)
pixmapPainter.fillRect(c*12, r*10, 12, 10, pixmapPainter.pen().color());
else if ((m_decoder->cellBold(r, c) || m_decoder->cellItalic(r, c)) && characterSet < 24)
drawBoldOrItalicCharacter(pixmapPainter, r, c, characterCode, characterSet, characterFragment);
2020-09-06 16:47:38 +01:00
else
drawFromFontBitmap(pixmapPainter, r, c, characterCode, characterSet, characterFragment);
if (m_decoder->cellUnderlined(r, c) && !dontUnderline)
switch (characterFragment) {
case TeletextPageDecode::NormalSize:
case TeletextPageDecode::DoubleWidthLeftHalf:
case TeletextPageDecode::DoubleWidthRightHalf:
pixmapPainter.drawLine(c*12, r*10+9, c*12+11, r*10+9);
break;
case TeletextPageDecode::DoubleHeightBottomHalf:
case TeletextPageDecode::DoubleSizeBottomLeftQuarter:
case TeletextPageDecode::DoubleSizeBottomRightQuarter:
pixmapPainter.drawRect(c*12, r*10+8, 11, 1);
break;
default:
break;
}
2020-09-06 16:47:38 +01:00
if (characterDiacritical != 0) {
pixmapPainter.setCompositionMode(QPainter::CompositionMode_SourceOver);
pixmapPainter.setBackgroundMode(Qt::TransparentMode);
drawFromFontBitmap(pixmapPainter, r, c, characterDiacritical+64, 7, characterFragment);
pixmapPainter.setBackgroundMode(Qt::OpaqueMode);
2020-09-06 16:47:38 +01:00
}
if (pixmapPainter.compositionMode() != QPainter::CompositionMode_SourceOver)
pixmapPainter.setCompositionMode(QPainter::CompositionMode_SourceOver);
2020-09-06 16:47:38 +01:00
}
inline void TeletextPageRender::drawBoldOrItalicCharacter(QPainter &pixmapPainter, int r, int c, unsigned char characterCode, int characterSet, TeletextPageDecode::CharacterFragment characterFragment)
{
QBitmap bitmap = QBitmap(12, 10);
QPainter bitmapPainter;
// TODO italic glyph-making is VERY slow!
if (m_decoder->cellItalic(r, c)) {
bitmap.clear();
bitmapPainter.begin(&bitmap);
bitmapPainter.setBackgroundMode(Qt::OpaqueMode);
bitmapPainter.drawPixmap(1, 0, *m_fontBitmap.rawBitmap(), (characterCode-32)*12, characterSet*10, 11, 3);
bitmapPainter.drawPixmap(0, 3, *m_fontBitmap.rawBitmap(), (characterCode-32)*12, characterSet*10+3, 12, 3);
bitmapPainter.drawPixmap(0, 6, *m_fontBitmap.rawBitmap(), (characterCode-32)*12+1, characterSet*10+6, 11, 4);
bitmapPainter.end();
} else
bitmap = m_fontBitmap.rawBitmap()->copy((characterCode-32)*12, characterSet*10, 12, 10);
if (m_decoder->cellBold(r, c)) {
QBitmap boldeningBitmap;
boldeningBitmap = bitmap.copy();
bitmapPainter.begin(&bitmap);
// No idea why we need this setPen workaround when character is made italic first?!
if (!m_decoder->cellItalic(r, c))
bitmapPainter.setPen(Qt::color0);
bitmapPainter.drawPixmap(1, 0, boldeningBitmap);
bitmapPainter.end();
}
drawFromBitmap(pixmapPainter, r, c, bitmap, characterFragment);
}
void TeletextPageRender::renderPage(bool force)
2020-09-06 16:47:38 +01:00
{
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 pixmapPainter;
int flashingRow = 0;
bool rowRefreshed = false;
pixmapPainter.begin(m_pagePixmap[ph]);
pixmapPainter.setBackgroundMode(Qt::OpaqueMode);
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
}
if (ph == 0) {
if (m_decoder->cellFlashMode(r, c) != 0)
flashingRow = qMax(flashingRow, (m_decoder->cellFlashRatePhase(r, c) == 0) ? 1 : 2);
} 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) {
unsigned char characterCode;
int characterSet, characterDiacritical;
rowRefreshed = true;
if (!m_reveal && m_decoder->cellConceal(r, c)) {
characterCode = 0x20;
characterSet = 0;
characterDiacritical = 0;
} else {
characterCode = m_decoder->cellCharacterCode(r, c);
characterSet = m_decoder->cellCharacterSet(r, c);
characterDiacritical = m_decoder->cellCharacterDiacritical(r, c);
}
2020-09-06 16:47:38 +01:00
if (m_decoder->cellFlashMode(r, c) == 0)
pixmapPainter.setPen(m_decoder->cellForegroundQColor(r, c));
else {
// Flashing cell, decide if phase in this cycle is on or off
bool phaseOn;
2020-09-06 16:47:38 +01:00
if (m_decoder->cellFlashRatePhase(r, c) == 0)
phaseOn = (ph < 3) ^ (m_decoder->cellFlashMode(r, c) == 2);
else
phaseOn = ((ph == m_decoder->cellFlash2HzPhaseNumber(r, c)-1) || (ph == m_decoder->cellFlash2HzPhaseNumber(r, c)+2)) ^ (m_decoder->cellFlashMode(r, c) == 2);
2020-09-06 16:47:38 +01:00
// If flashing to adjacent CLUT select the appropriate foreground colour
if (m_decoder->cellFlashMode(r, c) == 3 && !phaseOn)
pixmapPainter.setPen(m_decoder->cellFlashForegroundQColor(r, c));
else
pixmapPainter.setPen(m_decoder->cellForegroundQColor(r, c));
// If flashing mode is Normal or Invert, draw a space instead of a character on phase
if ((m_decoder->cellFlashMode(r, c) == 1 || m_decoder->cellFlashMode(r, c) == 2) && !phaseOn) {
// Character 0x00 draws space without underline
characterCode = 0x00;
characterSet = 0;
characterDiacritical = 0;
2020-09-06 16:47:38 +01:00
}
}
2020-09-06 16:47:38 +01:00
if (!m_mix || m_decoder->cellBoxed(r, c))
pixmapPainter.setBackground(m_decoder->cellBackgroundQColor(r, c));
else
pixmapPainter.setBackground(Qt::transparent);
2020-09-06 16:47:38 +01:00
drawCharacter(pixmapPainter, r, c, characterCode, characterSet, characterDiacritical, 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) {
pixmapPainter.setBackground(QColor(0, 0, 0, 128));
pixmapPainter.setPen(QColor(255, 255, 255, 224));
pixmapPainter.drawPixmap(c*12, r*10, *m_fontBitmap.rawBitmap(), (m_decoder->teletextPage()->character(r, c)+32)*12, 250, 12, 10);
}
}
}
2020-09-06 16:47:38 +01:00
pixmapPainter.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) {
pixmapPainter.begin(m_pagePixmap[3]);
pixmapPainter.setCompositionMode(QPainter::CompositionMode_Source);
pixmapPainter.drawPixmap(0, r*10, *m_pagePixmap[0], 0, r*10, 864, 10);
pixmapPainter.end();
renderRow(r, 3);
2020-09-06 16:47:38 +01:00
if (m_flashBuffersHz == 2) {
pixmapPainter.begin(m_pagePixmap[1]);
pixmapPainter.setCompositionMode(QPainter::CompositionMode_Source);
pixmapPainter.drawPixmap(0, r*10, *m_pagePixmap[0], 0, r*10, 864, 10);
pixmapPainter.end();
pixmapPainter.begin(m_pagePixmap[2]);
pixmapPainter.setCompositionMode(QPainter::CompositionMode_Source);
pixmapPainter.drawPixmap(0, r*10, *m_pagePixmap[0], 0, r*10, 864, 10);
pixmapPainter.end();
pixmapPainter.begin(m_pagePixmap[4]);
pixmapPainter.setCompositionMode(QPainter::CompositionMode_Source);
pixmapPainter.drawPixmap(0, r*10, *m_pagePixmap[3], 0, r*10, 864, 10);
pixmapPainter.end();
pixmapPainter.begin(m_pagePixmap[5]);
pixmapPainter.setCompositionMode(QPainter::CompositionMode_Source);
pixmapPainter.drawPixmap(0, r*10, *m_pagePixmap[3], 0, r*10, 864, 10);
pixmapPainter.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_pagePixmap[3] = m_pagePixmap[0]->copy();
if (rowFlashHz == 2) {
*m_pagePixmap[1] = m_pagePixmap[0]->copy();
*m_pagePixmap[2] = m_pagePixmap[0]->copy();
*m_pagePixmap[4] = m_pagePixmap[3]->copy();
*m_pagePixmap[5] = m_pagePixmap[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::setMix(bool mix)
2020-09-06 16:47:38 +01:00
{
if (mix == m_mix)
return;
2020-09-06 16:47:38 +01:00
m_mix = mix;
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->cellBoxed(r, 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
}