2015-12-06 17:00:29 -05:00
# include "imageutil.h"
# include "util/misc.h"
2017-06-09 17:29:39 -04:00
# include "util/rsscopetimer.h"
2015-12-06 17:00:29 -05:00
2016-09-24 10:12:44 -04:00
# include <QApplication>
2015-12-06 17:00:29 -05:00
# include <QByteArray>
# include <QImage>
2016-09-24 10:12:44 -04:00
# include <QMessageBox>
# include <QString>
# include <QTextCursor>
2015-12-06 17:00:29 -05:00
# include <QTextDocumentFragment>
2017-06-08 17:00:33 -04:00
# include <QBuffer>
# include <QtGlobal>
# include <QtMath>
2017-06-09 17:29:39 -04:00
# include <QSet>
2017-06-08 17:00:33 -04:00
# include <iostream>
2015-12-06 17:00:29 -05:00
ImageUtil : : ImageUtil ( ) { }
void ImageUtil : : extractImage ( QWidget * window , QTextCursor cursor )
{
cursor . movePosition ( QTextCursor : : Left , QTextCursor : : MoveAnchor , 1 ) ;
cursor . movePosition ( QTextCursor : : Right , QTextCursor : : KeepAnchor , 2 ) ;
QString imagestr = cursor . selection ( ) . toHtml ( ) ;
bool success = false ;
int start = imagestr . indexOf ( " base64, " ) + 7 ;
int stop = imagestr . indexOf ( " \" " , start ) ;
int length = stop - start ;
if ( ( start > = 0 ) & & ( length > 0 ) )
{
QByteArray ba = QByteArray : : fromBase64 ( imagestr . mid ( start , length ) . toLatin1 ( ) ) ;
QImage image = QImage : : fromData ( ba ) ;
if ( ! image . isNull ( ) )
{
QString file ;
success = true ;
if ( misc : : getSaveFileName ( window , RshareSettings : : LASTDIR_IMAGES , " Save Picture File " , " Pictures (*.png *.xpm *.jpg) " , file ) )
{
if ( ! image . save ( file , 0 , 100 ) )
if ( ! image . save ( file + " .png " , 0 , 100 ) )
QMessageBox : : warning ( window , QApplication : : translate ( " ImageUtil " , " Save image " ) , QApplication : : translate ( " ImageUtil " , " Cannot save the image, invalid filename " ) ) ;
}
}
}
if ( ! success )
{
QMessageBox : : warning ( window , QApplication : : translate ( " ImageUtil " , " Save image " ) , QApplication : : translate ( " ImageUtil " , " Not an image " ) ) ;
}
}
2015-12-12 16:41:15 -05:00
2017-06-08 17:00:33 -04:00
void ImageUtil : : optimizeSize ( QString & html , const QImage & original , QImage & optimized , int maxPixels , int maxBytes )
{
2017-06-09 19:43:02 -04:00
//nothing to do if it fits into the limits
2017-06-08 17:00:33 -04:00
optimized = original ;
if ( ( maxPixels < = 0 ) | | ( optimized . width ( ) * optimized . height ( ) < = maxPixels ) ) {
if ( checkSize ( html , optimized , maxBytes ) ) {
return ;
}
}
2017-06-09 17:29:39 -04:00
QVector < QRgb > ct ;
quantization ( original , ct ) ;
2017-06-08 17:00:33 -04:00
//Downscale the image to fit into maxPixels
qreal scale = qSqrt ( ( qreal ) ( maxPixels ) / original . width ( ) / original . height ( ) ) ;
if ( scale > 1.0 ) scale = 1.0 ;
//Half the resolution until image fits into maxBytes, or width becomes 0
2017-06-09 19:43:02 -04:00
bool success = false ;
2017-06-08 17:00:33 -04:00
do {
2017-06-09 17:29:39 -04:00
optimized = original . scaledToWidth ( ( int ) ( original . width ( ) * scale ) , Qt : : SmoothTransformation ) . convertToFormat ( QImage : : Format_Indexed8 , ct ) ;
2017-06-09 19:43:02 -04:00
success = checkSize ( html , optimized , maxBytes ) ;
2017-06-08 17:00:33 -04:00
scale = scale / 2.0 ;
2017-06-09 19:43:02 -04:00
} while ( ( ! optimized . isNull ( ) ) & & ! success ) ;
2017-06-08 17:00:33 -04:00
}
bool ImageUtil : : checkSize ( QString & embeddedImage , const QImage & img , int maxBytes )
{
2017-06-09 19:43:02 -04:00
RsScopeTimer st ( " Check size " ) ;
//0 means no limit
if ( maxBytes > 500 )
maxBytes - = 500 ; //make place for html stuff
else if ( maxBytes > 0 ) {
std : : cerr < < QString ( " Limit is too small nothing will fit in, limit: %1 bytes \n " ) . arg ( maxBytes ) . toStdString ( ) ;
return false ;
}
2017-06-08 17:00:33 -04:00
QByteArray bytearray ;
QBuffer buffer ( & bytearray ) ;
2017-06-09 19:43:02 -04:00
bool success = false ;
2017-06-08 17:00:33 -04:00
std : : cout < < QString ( " Trying image: format PNG, size %1x%2, colors %3 \n " ) . arg ( img . width ( ) ) . arg ( img . height ( ) ) . arg ( img . colorCount ( ) ) . toStdString ( ) ;
if ( buffer . open ( QIODevice : : WriteOnly ) ) {
if ( img . save ( & buffer , " PNG " , 0 ) ) {
2017-06-09 19:43:02 -04:00
if ( ( maxBytes > 0 ) & & ( bytearray . length ( ) * 4 / 3 > maxBytes ) ) // *4/3 for base64
2017-06-08 17:00:33 -04:00
{
2017-06-09 19:43:02 -04:00
std : : cout < < QString ( " \t Too large, size: %1, limit: %2 bytes \n " ) . arg ( bytearray . length ( ) * 4 / 3 ) . arg ( maxBytes ) . toStdString ( ) ;
2017-06-08 17:00:33 -04:00
} else {
2017-06-09 19:43:02 -04:00
std : : cout < < QString ( " \t OK, size: %1, limit: %2 bytes \n " ) . arg ( bytearray . length ( ) * 4 / 3 ) . arg ( maxBytes ) . toStdString ( ) ;
success = true ;
QByteArray encodedByteArray = bytearray . toBase64 ( ) ;
embeddedImage = " <img src= \" data:image/png;base64, " ;
embeddedImage . append ( encodedByteArray ) ;
embeddedImage . append ( " \" > " ) ;
2017-06-08 17:00:33 -04:00
}
} else {
std : : cerr < < " ImageUtil: image can't be saved to buffer " < < std : : endl ;
}
buffer . close ( ) ;
bytearray . clear ( ) ;
} else {
std : : cerr < < " ImageUtil: buffer can't be opened " < < std : : endl ;
}
2017-06-09 19:43:02 -04:00
return success ;
2017-06-08 17:00:33 -04:00
}
2017-06-09 17:29:39 -04:00
bool redLessThan ( const QRgb & c1 , const QRgb & c2 )
{
return qRed ( c1 ) < qRed ( c2 ) ;
}
bool greenLessThan ( const QRgb & c1 , const QRgb & c2 )
{
return qGreen ( c1 ) < qGreen ( c2 ) ;
}
bool blueLessThan ( const QRgb & c1 , const QRgb & c2 )
{
return qBlue ( c1 ) < qBlue ( c2 ) ;
}
//median cut algoritmh
void ImageUtil : : quantization ( const QImage & img , QVector < QRgb > & palette )
{
int bits = 4 ; // bits/pixel
2017-06-09 19:43:02 -04:00
int samplesize = 100000 ; //only take this many color samples
2017-06-09 17:29:39 -04:00
RsScopeTimer st ( " Quantization " ) ;
QSet < QRgb > colors ;
//collect color information
2017-06-09 19:43:02 -04:00
int imgsize = img . width ( ) * img . height ( ) ;
int width = img . width ( ) ;
samplesize = qMin ( samplesize , imgsize ) ;
double sampledist = ( double ) imgsize / ( double ) samplesize ;
for ( double i = 0 ; i < imgsize ; i + = sampledist ) {
QRgb pixel = img . pixel ( ( int ) i % width , ( int ) i / width ) ;
colors . insert ( pixel ) ;
2017-06-09 17:29:39 -04:00
}
QList < QRgb > colorlist = colors . toList ( ) ;
//don't do the algoritmh if we have less than 16 different colors
if ( colorlist . size ( ) < = ( 1 < < bits ) ) {
for ( int i = 0 ; i < colors . count ( ) ; + + i )
palette . append ( colorlist [ i ] ) ;
} else {
quantization ( colorlist . begin ( ) , colorlist . end ( ) , bits , palette ) ;
}
}
void ImageUtil : : quantization ( QList < QRgb > : : iterator begin , QList < QRgb > : : iterator end , int depth , QVector < QRgb > & palette )
{
//the buckets are ready
if ( depth = = 0 ) {
avgbucket ( begin , end , palette ) ;
return ;
}
//nothing to do
int count = end - begin ;
if ( count = = 1 ) {
palette . append ( * begin ) ;
return ;
}
//widest color channel
int rl = 255 ;
int gl = 255 ;
int bl = 255 ;
int rh = 0 ;
int gh = 0 ;
int bh = 0 ;
for ( QList < QRgb > : : iterator it = begin ; it < end ; + + it ) {
rl = qMin ( rl , qRed ( * it ) ) ;
gl = qMin ( gl , qGreen ( * it ) ) ;
bl = qMin ( bl , qBlue ( * it ) ) ;
rh = qMax ( rh , qRed ( * it ) ) ;
gh = qMax ( gh , qGreen ( * it ) ) ;
bh = qMax ( bh , qBlue ( * it ) ) ;
}
int red = rh - rl ;
int green = gh - gl ;
int blue = bh - bl ;
//order by the widest channel
if ( red > green )
if ( red > blue )
qSort ( begin , end , redLessThan ) ;
else
qSort ( begin , end , blueLessThan ) ;
else
if ( green > blue )
qSort ( begin , end , greenLessThan ) ;
else
qSort ( begin , end , blueLessThan ) ;
//split into two buckets
QList < QRgb > : : iterator split = begin + count / 2 ;
quantization ( begin , split , depth - 1 , palette ) ;
quantization ( split , end , depth - 1 , palette ) ;
}
void ImageUtil : : avgbucket ( QList < QRgb > : : iterator begin , QList < QRgb > : : iterator end , QVector < QRgb > & palette )
{
int red = 0 ;
int green = 0 ;
int blue = 0 ;
int count = end - begin ;
for ( QList < QRgb > : : iterator it = begin ; it < end ; + + it ) {
red + = qRed ( * it ) ;
green + = qGreen ( * it ) ;
blue + = qBlue ( * it ) ;
}
QRgb color = qRgb ( red / count , green / count , blue / count ) ;
palette . append ( color ) ;
}