mirror of
				https://github.com/f4exb/sdrangel.git
				synced 2025-11-04 05:30:32 -05:00 
			
		
		
		
	
		
			
	
	
		
			312 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
		
		
			
		
	
	
			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;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								}
							 |