1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the tools applications of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 or version 3 as published by the Free
20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and
21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the
22 ** following information to ensure the GNU Lesser General Public License
23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 **
26 ** As a special exception, The Qt Company gives you certain additional
27 ** rights. These rights are described in The Qt Company LGPL Exception
28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 **
30 ** GNU General Public License Usage
31 ** Alternatively, this file may be used under the terms of the GNU
32 ** General Public License version 3.0 as published by the Free Software
33 ** Foundation and appearing in the file LICENSE.GPL included in the
34 ** packaging of this file.  Please review the following information to
35 ** ensure the GNU General Public License version 3.0 requirements will be
36 ** met: http://www.gnu.org/copyleft/gpl.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 
42 #include "qanimationwriter.h"
43 #include <QFile>
44 #include <QString>
45 #include <QPainter>
46 #include <png.h>
47 #include <limits.h>
48 #include <netinet/in.h> // for htonl
49 
50 #ifdef QT_LINUXBASE
51 #  include <arpa/inet.h> // for htonl (LSB only)
52 #endif
53 
54 QT_BEGIN_NAMESPACE
55 
56 class QAnimationWriterData
57 {
58 public:
QAnimationWriterData(QIODevice * d)59     QAnimationWriterData(QIODevice* d) : framerate(1000), dev(d) {}
setFrameRate(int d)60     void setFrameRate(int d) { framerate = d; }
~QAnimationWriterData()61     virtual ~QAnimationWriterData() { }
62     virtual void setImage(const QImage& src)=0;
canCompose() const63     virtual bool canCompose() const { return false; }
composeImage(const QImage &,const QPoint &)64     virtual void composeImage(const QImage&, const QPoint& ) {}
65 
66 protected:
67     int framerate;
68     QIODevice* dev;
69 };
70 
71 
72 class QAnimationWriterMNG : public QAnimationWriterData {
73     bool first;
74     png_structp png_ptr;
75     png_infop info_ptr;
76 public:
QAnimationWriterMNG(QIODevice * d)77     QAnimationWriterMNG(QIODevice* d) : QAnimationWriterData(d)
78     {
79         first = true;
80         begin_png();
81     }
82 
~QAnimationWriterMNG()83     ~QAnimationWriterMNG()
84     {
85         if (first) {
86             // Eh? Not images.
87             QImage dummy(1,1,QImage::Format_RGB32);
88             setImage(dummy);
89         }
90         writeMEND();
91         end_png();
92     }
93 
begin_png()94     void begin_png()
95     {
96         png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,0,0,0);
97         info_ptr = png_create_info_struct(png_ptr);
98         png_set_compression_level(png_ptr,9);
99         png_set_write_fn(png_ptr, (void*)this, write, 0);
100     }
101 
end_png()102     void end_png()
103     {
104         png_destroy_write_struct(&png_ptr, &info_ptr);
105     }
106 
write(png_structp png_ptr,png_bytep data,png_size_t length)107     static void write( png_structp png_ptr, png_bytep data, png_size_t length)
108     {
109         QAnimationWriterMNG* that = (QAnimationWriterMNG*)png_get_io_ptr(png_ptr);
110         /*uint nw =*/ that->dev->write((const char*)data,length);
111     }
112 
writePNG(const QImage & image)113     void writePNG(const QImage& image)
114     {
115 #if !defined(QT_LINUXBASE) && \
116     (PNG_LIBPNG_VER_MAJOR < 1 || (PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR <= 4))
117         // LSB disallows accessing the info_ptr directly. LSB's png_set_IHDR sets
118         // the channels anyways, so just comment it out for LSB usage.
119         // In libpng >= 1.5, the png_info struct is no longer exported.
120         info_ptr->channels = 4;
121 #endif
122         png_set_sig_bytes(png_ptr, 8); // Pretend we already wrote the sig
123         png_set_IHDR(png_ptr, info_ptr, image.width(), image.height(),
124                      8, image.hasAlphaChannel()
125                      ? PNG_COLOR_TYPE_RGB_ALPHA : PNG_COLOR_TYPE_RGB,
126                      0, 0, 0);
127         png_write_info(png_ptr, info_ptr);
128         if (!image.hasAlphaChannel())
129             png_set_filler(png_ptr, 0,
130                            QSysInfo::ByteOrder == QSysInfo::BigEndian ?
131                            PNG_FILLER_BEFORE : PNG_FILLER_AFTER);
132         //if ( QImage::systemByteOrder() == QImage::BigEndian ) {
133         //png_set_swap_alpha(png_ptr);
134         //}
135         if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) {
136             png_set_bgr(png_ptr);
137         }
138 
139         int height = image.height();
140         png_bytep *row_pointers = new png_bytep[height];
141         for (int i = 0; i < height; ++i)
142             row_pointers[i] = (png_bytep)image.scanLine(i);
143         png_write_image(png_ptr, row_pointers);
144         delete [] row_pointers;
145         png_write_end(png_ptr, info_ptr);
146         end_png();
147         begin_png();
148     }
149 
writeMHDR(const QSize & size,int framerate)150     void writeMHDR(const QSize& size, int framerate)
151     {
152         dev->write("\212MNG\r\n\032\n", 8);
153 
154         struct {
155             int width;
156             int height;
157             int framerate;
158             int a,b,c;
159             int profile;
160         } chunk;
161         chunk.width = htonl(size.width());
162         chunk.height = htonl(size.height());
163         chunk.framerate = htonl(framerate);
164         chunk.a=0;
165         chunk.b=0;
166         chunk.c=0;
167         chunk.profile = htonl(0x00000003);
168 
169         png_write_chunk(png_ptr, (png_byte*)"MHDR", (png_byte*)&chunk, sizeof(chunk));
170     }
171 
writeMEND()172     void writeMEND()
173     {
174         png_write_chunk(png_ptr, (png_byte*)"MEND", 0, 0);
175     }
176 
writeDEFI(const QPoint & offset,const QSize &)177     void writeDEFI(const QPoint& offset, const QSize& /*size*/)
178     {
179         struct {
180             ushort o;
181             uchar s;
182             uchar concrete;
183             int x,y;
184             int lc,rc,tc,bc;
185         } chunk;
186         chunk.o=0;
187         chunk.s=0;
188         chunk.concrete=1;
189         chunk.x=htonl(offset.x());
190         chunk.y=htonl(offset.y());
191         chunk.lc=0;
192         chunk.rc=0;
193         chunk.tc=htonl(INT_MAX);
194         chunk.bc=htonl(INT_MAX);
195 
196         png_write_chunk(png_ptr, (png_byte*)"DEFI", (png_byte*)&chunk, sizeof(chunk));
197     }
198 
writeFRAM(const QSize & size)199     void writeFRAM(const QSize& size)
200     {
201         struct {
202             uchar mode;
203             uchar n;
204             uchar nu;
205             uchar d;
206             uchar t;
207             uchar clip;
208             uchar s;
209             uchar deltatype;
210             uint left;
211             uint right;
212             uint top;
213             uint bottom;
214         } chunk;
215         chunk.mode=1;
216         chunk.n='a';
217         chunk.nu=0;
218         chunk.d=0;
219         chunk.clip=1;
220         chunk.t=0;
221         chunk.s=0;
222         chunk.deltatype=0;
223         chunk.left=0;
224         chunk.right=htonl(size.width());
225         chunk.top=0;
226         chunk.bottom=htonl(size.height());
227 
228         png_write_chunk(png_ptr, (png_byte*)"FRAM", (png_byte*)&chunk, sizeof(chunk));
229     }
230 
writeMOVE(const QPoint & offset)231     void writeMOVE(const QPoint& offset)
232     {
233         struct {
234             uchar filler[3];
235             uchar z[5];
236             int x,y;
237         } chunk;
238         memset(chunk.z,0,5);
239         chunk.x=htonl(offset.x());
240         chunk.y=htonl(offset.y());
241 
242         png_write_chunk(png_ptr, (png_byte*)"MOVE", ((png_byte*)&chunk)+3, sizeof(chunk)-3);
243     }
244 
setImage(const QImage & src)245     void setImage(const QImage& src)
246     {
247         if (first) {
248             first = false;
249             writeMHDR(src.size(),framerate);
250         }
251         composeImage(src,QPoint(0,0));
252     }
253 
canCompose() const254     bool canCompose() const { return true; }
255 
composeImage(const QImage & src,const QPoint & offset)256     void composeImage(const QImage& src, const QPoint& offset)
257     {
258         writeMOVE(offset);
259         //writeFRAM(src.size());
260         writePNG(src);
261     }
262 };
263 
QAnimationWriter(const QString & filename,const char * format)264 QAnimationWriter::QAnimationWriter(const QString& filename, const char* format)
265 {
266     if (qstrncmp(format, "MNG", 4)) {
267 	qWarning("Format \"%s\" not supported, only MNG", format);
268 	dev = 0;
269 	d = 0;
270     } else {
271 	QFile *f = new QFile(filename);
272 	f->open(QIODevice::WriteOnly);
273 	dev = f;
274 	d = new QAnimationWriterMNG(dev);
275     }
276 }
277 
okay() const278 bool QAnimationWriter::okay() const
279 {
280     if (!dev)
281         return false;
282     QFile *file = qobject_cast<QFile*>(dev);
283     Q_ASSERT(file);
284     return (file->error() == QFile::NoError);
285 }
286 
~QAnimationWriter()287 QAnimationWriter::~QAnimationWriter()
288 {
289     delete d;
290     delete dev;
291 }
292 
setFrameRate(int r)293 void QAnimationWriter::setFrameRate(int r)
294 {
295     if (d)
296         d->setFrameRate(r);
297 }
298 
appendFrame(const QImage & frm,const QPoint & offset)299 void QAnimationWriter::appendFrame(const QImage& frm, const QPoint& offset)
300 {
301     if (!dev)
302         return;
303 
304     const QImage frame = frm.convertToFormat(QImage::Format_RGB32);
305     const int alignx = 1;
306     if (prev.isNull() || !d->canCompose()) {
307         d->setImage(frame);
308     } else {
309         bool done;
310         int minx, maxx, miny, maxy;
311         int w = frame.width();
312         int h = frame.height();
313 
314         const quint32 *framePtr = reinterpret_cast<const quint32*>(frame.bits());
315         const quint32 *prevPtr = reinterpret_cast<const quint32*>(prev.bits());
316         const int frameStride = frame.bytesPerLine() / sizeof(quint32);
317         const int prevStride = prev.bytesPerLine() / sizeof(quint32);
318 
319         // Find left edge of change
320         done = false;
321         for (minx = 0; minx < w && !done; ++minx) {
322             const quint32 *p1 = framePtr + minx;
323             const quint32 *p2 = prevPtr + minx + offset.x();
324             for (int y = 0; y < h; ++y) {
325                 if (*p1 != *p2) {
326                     done = true;
327                     break;
328                 }
329                 p1 += frameStride;
330                 p2 += prevStride;
331             }
332         }
333         --minx;
334 
335         // Find right edge of change
336         done = false;
337         for (maxx = w-1; maxx >= 0 && !done; --maxx) {
338             const quint32 *p1 = framePtr + maxx;
339             const quint32 *p2 = prevPtr + maxx + offset.x();
340             for (int y = 0; y < h; ++y) {
341                 if (*p1 != *p2) {
342                     done = true;
343                     break;
344                 }
345                 p1 += frameStride;
346                 p2 += prevStride;
347             }
348         }
349         ++maxx;
350 
351         // Find top edge of change
352         done = false;
353         for (miny = 0; miny < h && !done; ++miny) {
354             const quint32 *p1 = framePtr + miny * frameStride;
355             const quint32 *p2 = prevPtr + miny * prevStride + offset.x();
356             for (int x = 0; x < w; ++x) {
357                 if (*p1 != *p2) {
358                     done = true;
359                     break;
360                 }
361                 ++p1;
362                 ++p2;
363             }
364         }
365         --miny;
366 
367         // Find right edge of change
368         done = false;
369         for (maxy = h-1; maxy >= 0 && !done; --maxy) {
370             const quint32 *p1 = framePtr + maxy * frameStride;
371             const quint32 *p2 = prevPtr + maxy * prevStride + offset.x();
372             for (int x = 0; x < w; ++x) {
373                 if (*p1 != *p2) {
374                     done = true;
375                     break;
376                 }
377                 ++p1;
378                 ++p2;
379             }
380         }
381         ++maxy;
382 
383         if (minx > maxx)
384             minx = maxx = 0;
385         if (miny > maxy)
386             miny = maxy = 0;
387 
388         if (alignx > 1) {
389             minx -= minx % alignx;
390             maxx = maxx - maxx % alignx + alignx - 1;
391         }
392 
393         int dw = maxx - minx + 1;
394         int dh = maxy - miny + 1;
395 
396         QImage diff(dw, dh, QImage::Format_ARGB32);
397 
398         int x, y;
399         for (y = 0; y < dh; ++y) {
400             QRgb* li = (QRgb*)frame.scanLine(y+miny)+minx;
401             QRgb* lp = (QRgb*)prev.scanLine(y+miny+offset.y())+minx+offset.x();
402             QRgb* ld = (QRgb*)diff.scanLine(y);
403             if (alignx) {
404                 for (x = 0; x < dw; x += alignx) {
405                     int i;
406                     for (i = 0; i < alignx; ++i) {
407                         if (li[x+i] != lp[x+i])
408                             break;
409                     }
410                     if (i == alignx) {
411                         // All the same
412                         for (i = 0; i < alignx; ++i)
413                             ld[x+i] = qRgba(0,0,0,0);
414                     } else {
415                         // Some different
416                         for (i = 0; i < alignx; ++i)
417                             ld[x+i] = 0xff000000 | li[x+i];
418                     }
419                 }
420             } else {
421                 for (x = 0; x < dw; ++x) {
422                     if (li[x] != lp[x])
423                         ld[x] = 0xff000000 | li[x];
424                     else
425                         ld[x] = qRgba(0,0,0,0);
426                 }
427             }
428         }
429 
430         d->composeImage(diff, QPoint(minx, miny) + offset);
431     }
432     if (prev.isNull() || (prev.size() == frame.size() && offset == QPoint(0,0))) {
433         prev = frame;
434     } else {
435         QPainter p(&prev);
436         p.drawImage(offset.x(), offset.y(), frame, 0, 0,
437                     frame.width(), frame.height());
438     }
439 }
440 
appendFrame(const QImage & frm)441 void QAnimationWriter::appendFrame(const QImage& frm)
442 {
443     appendFrame(frm, QPoint(0,0));
444 }
445 
appendBlankFrame()446 void QAnimationWriter::appendBlankFrame()
447 {
448     QImage i(1,1,QImage::Format_ARGB32);
449     i.fill(0);
450     d->composeImage(i, QPoint(0,0));
451 }
452 
453 QT_END_NAMESPACE
454