1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-11-05 00:11:16 -05:00
sdrangel/sdrbase/util/png.cpp
Jon Beniston cddc8c9b83 Star Tracker updates.
Plot Sun and Moon on sky temperature chart.
Plot markers on Galactic line-of-sight chart.
Create animations from Galactic line-of-sight chart.
Allow weather at antenna location to be downloaded from openweathermap.org
Allow target to be entered as Galactic longitude / latitude.
Add azimuth and elevation offsets to support scans around targets.
Add S7, S8 and S9 targets.
Refactor some code from GUI to main plugin, so computed values can be used in other plugins.
2021-10-12 11:07:56 +01:00

312 lines
7.6 KiB
C++

///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "png.h"
#include <QDebug>
#include <QString>
#include <QBuffer>
#include <QFile>
PNG::PNG()
{
}
PNG::PNG(QByteArray data) :
m_bytes(data),
m_width(0),
m_height(0)
{
int idx = findChunk("IHDR");
if (idx >= 0)
{
m_width = getInt(idx + 8);
m_height = getInt(idx + 12);
}
else
{
qDebug() << "PNG: No IHDR found";
}
}
void PNG::appendSignature()
{
m_bytes.append(m_signature);
}
void PNG::appendEnd()
{
QByteArray ba;
appendChunk("IEND", ba);
}
void PNG::appendChunk(const char *type, QByteArray chunk)
{
appendInt(chunk.size());
appendInt(typeStringToInt(type));
m_bytes.append(chunk);
appendInt(crc(type, chunk)); // CRC type and data, but not length
}
void PNG::append(QByteArray data)
{
m_bytes.append(data);
}
void PNG::appendInt(QByteArray& ba, quint32 value)
{
// Network byte order
ba.append((value >> 24) & 0xff);
ba.append((value >> 16) & 0xff);
ba.append((value >> 8) & 0xff);
ba.append((value) & 0xff);
}
void PNG::appendShort(QByteArray& ba, quint16 value)
{
// Network byte order
ba.append((value >> 8) & 0xff);
ba.append((value) & 0xff);
}
void PNG::appendInt(quint32 value)
{
appendInt(m_bytes, value);
}
qint32 PNG::getInt(int index)
{
qint32 v = 0;
for (int i = 0; i < 4; i++) {
v |= (m_bytes[index+i] & 0xff) << ((3-i)*8);
}
return v;
}
qint32 PNG::crc(const char *type, const QByteArray data)
{
m_crc.init();
m_crc.calculate((const uint8_t *)type, 4);
m_crc.calculate((const uint8_t *)data.data(), data.size());
return m_crc.get();
}
qint32 PNG::typeStringToInt(const char *header)
{
quint32 v = 0;
for (int i = 0; i < 4; i++) {
v |= header[i] << ((3-i)*8);
}
return v;
}
QString PNG::intToTypeString(quint32 type)
{
QString s;
for (int i = 0; i < 4; i++)
{
char c = (type >> ((3-i)*8)) & 0xff;
s.append(c);
}
return s;
}
// Animation control chunk for APNGs (loops=0 is infinite)
void PNG::appendacTL(int frames, quint32 loops)
{
QByteArray ba;
appendInt(ba, frames);
appendInt(ba, loops);
appendChunk("acTL", ba);
}
// Frame control chunk for APNGs
void PNG::appendfcTL(quint32 seqNo, quint32 width, quint32 height, int fps, quint32 xOffset, quint32 yOffset)
{
QByteArray ba;
appendInt(ba, seqNo);
appendInt(ba, width);
appendInt(ba, height);
appendInt(ba, xOffset);
appendInt(ba, yOffset);
appendShort(ba, 1);
appendShort(ba, fps);
ba.append((char)0); // No disposal
ba.append((char)0); // Overwrite previous image
appendChunk("fcTL", ba);
}
// Animation frame data
void PNG::appendfdAT(quint32 seqNo, const QByteArray& data)
{
QByteArray ba;
appendInt(ba, seqNo);
ba.append(data);
appendChunk("fdAT", ba);
}
QByteArray PNG::data()
{
return m_bytes;
}
bool PNG::checkSignature()
{
return m_bytes.startsWith(m_signature);
}
int PNG::findChunk(const char *type, int startIndex)
{
if ((startIndex == 0) && !checkSignature())
{
qDebug() << "PNG::findChunk - PNG signature not found";
return -1;
}
int i = startIndex == 0 ? m_signature.size() : startIndex;
qint32 typeInt = typeStringToInt(type);
while (i < m_bytes.size())
{
qint32 chunkType = getInt(i+4);
if (typeInt == chunkType) {
return i;
}
qint32 length = getInt(i);
i += 12 + length;
}
return -1;
}
// Get chunk including length, type data and CRC
QByteArray PNG::getChunk(const char *type)
{
int start = findChunk(type);
if (start >= 0)
{
quint32 length = getInt(start);
return m_bytes.mid(start, length + 12);
}
return QByteArray();
}
// Get all chunks with same type
QByteArray PNG::getChunks(const char *type)
{
int start = 0;
QByteArray bytes;
while ((start = findChunk(type, start)) != -1)
{
quint32 length = getInt(start);
QByteArray chunk = m_bytes.mid(start, length + 12);
bytes.append(chunk);
start += chunk.size();
}
return bytes;
}
// Get data from chunk
QList<QByteArray> PNG::getChunkData(const char *type)
{
int start = 0;
QList<QByteArray> chunks;
while ((start = findChunk(type, start)) != -1)
{
quint32 length = getInt(start);
QByteArray chunk = m_bytes.mid(start + 8, length);
chunks.append(chunk);
start += length + 12;
}
return chunks;
}
quint32 PNG::getWidth() const
{
return m_width;
}
quint32 PNG::getHeight() const
{
return m_height;
}
bool APNG::addImage(const QImage& image, int fps)
{
if (!m_ended)
{
QByteArray ba;
QBuffer buffer(&ba);
buffer.open(QIODevice::ReadWrite);
if (image.save(&buffer, "PNG"))
{
PNG pngIn(ba);
if (m_frame == 0)
{
m_png.append(pngIn.getChunk("IHDR"));
m_png.appendacTL(m_frames);
m_png.appendfcTL(m_seqNo++, pngIn.getWidth(), pngIn.getHeight(), fps);
// PNGs can contain multiple IDAT chunks, typically each limited to 8kB
m_png.append(pngIn.getChunks("IDAT"));
}
else
{
m_png.appendfcTL(m_seqNo++, pngIn.getWidth(), pngIn.getHeight(), fps);
QList<QByteArray> data = pngIn.getChunkData("IDAT");
for (int i = 0; i < data.size(); i++) {
m_png.appendfdAT(m_seqNo++, data[i]);
}
}
m_frame++;
return true;
}
else
{
qDebug() << "APNG::addImage - Failed to save image to PNG";
return false;
}
}
else
{
qDebug() << "APNG::addImage - Call to addImage after IEND added";
return false;
}
}
bool APNG::save(const QString& fileName)
{
if (!m_ended)
{
if (m_frame != m_frames) {
qDebug() << "APNG::save - " << m_frame << " frames added out of expected " << m_frames;
}
m_png.appendEnd();
m_ended = true;
}
QFile animFile(fileName);
if (animFile.open(QIODevice::WriteOnly | QIODevice::Truncate))
{
animFile.write(m_png.data());
animFile.close();
return true;
}
else
{
return false;
}
}