Skip to content

Commit

Permalink
ADD: Do not modify original image as much as possible
Browse files Browse the repository at this point in the history
ADD: Point to a subarea of Image source, instead of actively cropping it, to avoid extra copying
CHANGE: Removing transparency and alpha is moved out of Image, as it is not mask related
  • Loading branch information
spillerrec committed Jul 19, 2017
1 parent 006ae45 commit c6adfe3
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 77 deletions.
3 changes: 3 additions & 0 deletions cgCompress.pro
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ CONFIG += c++14
# Generate both debug and release on Linux
CONFIG += debug_and_release

#QMAKE_CXXFLAGS_RELEASE -= -O2
QMAKE_CXXFLAGS_DEBUG += -O3

# Position of binaries and build files
Release:DESTDIR = release
Release:UI_DIR = release/.ui
Expand Down
21 changes: 21 additions & 0 deletions src/FileUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -167,3 +167,24 @@ QStringList expandFolders( QStringList paths ){

return files;
}


/** \return This image where all transparent pixels are set to discard_color */
QImage discardTransparent( QImage img, QRgb discard_color ){
QImage output( img.convertToFormat(QImage::Format_ARGB32) );

auto size = output.size();
for( int iy=0; iy<size.height(); iy++ ){
QRgb* out = (QRgb*)output.scanLine( iy );
for( int ix=0; ix<size.width(); ix++ )
if( qAlpha( out[ix] ) == 0 )
out[ix] = discard_color;
}

return output;
}

QImage withoutAlpha( QImage img ){
return img.convertToFormat(QImage::Format_RGB32).convertToFormat(QImage::Format_ARGB32);
}

3 changes: 3 additions & 0 deletions src/FileUtils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,8 @@ bool isSimilar( QImage img1, QImage img2 );

QStringList expandFolders( QStringList files );

QImage discardTransparent( QImage img, QRgb discard_color = qRgb(0,0,0) );
QImage withoutAlpha( QImage img );

#endif

72 changes: 30 additions & 42 deletions src/Image.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ static QImage make_mask( QSize size ){
return mask;
}

Image::Image( QPoint pos, QImage img ) : pos(pos), img(img), mask(make_mask(img.size())) {
Image::Image( QPoint pos, QImage img )
: img(img.convertToFormat(QImage::Format_ARGB32), pos), mask(make_mask(img.size()))
{
mask.fill( PIXEL_DIFFERENT );
}

Expand All @@ -51,7 +53,7 @@ Image::Image( QPoint pos, QImage img ) : pos(pos), img(img), mask(make_mask(img.
Image Image::resize( int size ) const{
size = min( size, img.width() );
size = min( size, img.height() );
QImage scaled = img.scaled( size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation );
QImage scaled = qimg().scaled( size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation );
return Image( {0,0}, scaled );
}

Expand Down Expand Up @@ -97,34 +99,21 @@ QList<Image> Image::segment() const{
}
*/

/** \return This image where all transparent pixels are set to #000000 */
Image Image::discardTransparent() const{
QImage output( img.convertToFormat(QImage::Format_ARGB32) );

auto size = output.size();
for( int iy=0; iy<size.height(); iy++ ){
QRgb* out = (QRgb*)output.scanLine( iy );
for( int ix=0; ix<size.width(); ix++ )
if( qAlpha( out[ix] ) == 0 )
out[ix] = qRgba( 0,0,0,0 );
}

return Image( pos, output );
}

/** Make the area the other image covers transparent (colors are kept).
* \param [in] input The area to remove
* \return The resulting image */
Image Image::remove_area( Image input ) const{
QImage output( img.convertToFormat(QImage::Format_ARGB32) );
//TODO: Only affect the mask?
QImage output( qimg().convertToFormat(QImage::Format_ARGB32) );

for( int iy=input.pos.y(); iy<input.pos.y()+input.img.height(); iy++ ){
//TODO: This is wrong, it should be the difference!
for( int iy=input.get_pos().y(); iy<input.get_pos().y()+input.img.height(); iy++ ){
QRgb* out = (QRgb*)output.scanLine( iy );
for( int ix=input.pos.x(); ix<input.pos.x()+input.img.width(); ix++ )
for( int ix=input.get_pos().x(); ix<input.get_pos().x()+input.img.width(); ix++ )
out[ix] = qRgba( qRed(out[ix]),qGreen(out[ix]),qBlue(out[ix]),0 );
}

return Image( pos, output );
return Image( get_pos(), output );
}

/** Segment based on how the images differs.
Expand Down Expand Up @@ -163,15 +152,15 @@ QList<Image> Image::diff_segment( Image diff ) const{
* \param [in] on_top Image to paint
* \return The combined image */
Image Image::combine( Image on_top ) const{
QPoint tl{ min( pos.x(), on_top.pos.x() ), min( pos.y(), on_top.pos.y() ) };
int width = max( pos.x()+img.width(), on_top.pos.x()+on_top.img.width() ) - tl.x();
int height = max( pos.y()+img.height(), on_top.pos.y()+on_top.img.height() ) - tl.y();
QPoint tl{ min( get_pos().x(), on_top.get_pos().x() ), min( get_pos().y(), on_top.get_pos().y() ) };
int width = max( get_pos().x()+img.width(), on_top.get_pos().x()+on_top.img.width() ) - tl.x();
int height = max( get_pos().y()+img.height(), on_top.get_pos().y()+on_top.img.height() ) - tl.y();

QImage output( width, height, QImage::Format_ARGB32 );
output.fill( 0 );
QPainter painter( &output );
painter.drawImage( pos-tl, img );
painter.drawImage( on_top.pos-tl, on_top.img );
painter.drawImage( get_pos()-tl, qimg() );
painter.drawImage( on_top.get_pos()-tl, on_top.qimg() );

return Image( tl, output ); //TODO:
}
Expand All @@ -185,8 +174,8 @@ Image Image::difference( Image input ) const{
auto w = img.width();
auto mask = make_mask( img.size() );
for( int iy=0; iy<img.height(); iy++ ){
auto out = (const QRgb*) img.constScanLine( iy );
auto in = (const QRgb*)input.img.constScanLine( iy );
auto out = img.row( iy );
auto in = input.img.row( iy );
auto out_mask = mask.scanLine( iy );
for( int ix=0; ix<w; ix++ )
out_mask[ix] = (in[ix] == out[ix]) ? PIXEL_MATCH : PIXEL_DIFFERENT;
Expand All @@ -203,12 +192,13 @@ Image Image::contain_both( Image input ) const{
//TODO: images must be the same size and at same point
if( mask.isNull() ||input.mask.isNull() )
return Image( {0,0}, QImage() );
//TODO: Check the behaviour of this

QImage mask_output( mask );

for( int iy=0; iy<mask.height(); iy++ ){
auto in1 = (const QRgb*) img.constScanLine( iy + pos.y() ) + pos.x();
auto in2 = (const QRgb*)input.img.constScanLine( iy + input.pos.y() ) + input.pos.x();
auto in1 = img.row( iy );
auto in2 = input.img.row( iy );

auto mask1 = mask.constScanLine( iy );
auto mask2 = input.mask.constScanLine( iy );
Expand Down Expand Up @@ -305,11 +295,11 @@ Image Image::clean_alpha( int kernel_size, int threshold ) const{
}

/** \return This image where all transparent pixels are set to transparent black **/
Image Image::remove_transparent() const{
QImage Image::remove_transparent() const{
if( mask.isNull() )
return *this;
return qimg(); //Nothing is transparent

QImage output( img.convertToFormat(QImage::Format_ARGB32) );
QImage output( qimg() );
int width = output.width(), height = output.height();

for( int iy=0; iy<height; iy++ ){
Expand All @@ -321,9 +311,7 @@ Image Image::remove_transparent() const{
out[ix] = TRANS_SET;
}

Image out( pos, output );
out.saved_data = saved_data; //This is already with without transparent data
return out;
return output;
}

struct ContentMap{
Expand Down Expand Up @@ -379,7 +367,7 @@ Image Image::optimize_filesize( Format format ) const{
changeable++;
}
if( changeable == 0 )
return copy.remove_transparent();
return copy;

//Start with the basic image
Image best = copy;
Expand All @@ -397,9 +385,9 @@ Image Image::optimize_filesize( Format format ) const{

//Make sure filtering actually improved the situation
if( copy.save_compressed_size( format ) > best.save_compressed_size( format ) )
return best.remove_transparent();
return best;
else
return copy.remove_transparent();
return copy;
}

int Image::compressed_size( Format format, Format::Precision p ) const{
Expand All @@ -414,18 +402,18 @@ int Image::compressed_size( Format format, Format::Precision p ) const{
return saved_data.size();

//Calculate the gradient
return format.file_size( remove_transparent().img, p );
return format.file_size( remove_transparent(), p );
}

int Image::estimate_compressed_size( Format format ) const{
if( mask.isNull() )
return format.file_size( remove_transparent().qimg(), Format::LOW );
return format.file_size( remove_transparent(), Format::LOW );
int diffs = 0;

auto w = img.width();
for( int iy=0; iy<img.height(); iy++ ){
auto row_alpha = mask.constScanLine( iy );
auto row = (const QRgb*) img.constScanLine( iy );
auto row = img.row( iy );
for( int ix=1; ix<w; ix++ ){
auto alpha_left = row_alpha[ix-1] != PIXEL_DIFFERENT;
auto alpha_right = row_alpha[ix ] != PIXEL_DIFFERENT;
Expand Down
32 changes: 13 additions & 19 deletions src/Image.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@
#include <QByteArray>

#include "Format.hpp"
#include "SubQImage.hpp"

class Image {
private:
QPoint pos;
QImage img;
SubQImage img;
QImage mask;

QByteArray saved_data;
Expand All @@ -44,8 +44,8 @@ class Image {
Image( QString path ) : Image( QImage(path) ) { }

private:
Image( QPoint pos, QImage img, QImage mask ) : pos(pos), img(img), mask(mask) { }
Image newMask( QImage mask ) const{ return Image( pos, img, mask ); }
Image( SubQImage img, QImage mask ) : img(img), mask(mask) { }
Image newMask( QImage mask ) const{ return Image( img, mask ); }
/*
QList<Image> segment() const;
QList<Image> diff_segment( Image diff ) const;*/
Expand All @@ -61,26 +61,22 @@ class Image {
bool is_valid() const{ return !img.size().isEmpty(); }

/** \return The offset of the image */
QPoint get_pos() const{ return pos; }
QPoint get_pos() const{ return img.offset(); }

/** \return The image data */
QImage qimg() const{ return img; }

/** Remove alpha */
void removeAlpha()
{ img = img.convertToFormat(QImage::Format_RGB32).convertToFormat(QImage::Format_ARGB32); }
QImage qimg() const{ return img.get(); }

/** Save the image to the file system
* \param [in] path The location on the file system
* \param [in] format The format used for compression
* \return true if successful */
bool save( QString path, Format format ) const{ return format.save( img, path ); }
bool save( QString path, Format format ) const{ return format.save( remove_transparent(), path ); }

/** Save the image to a memory buffer
* \param [in] format The compression format to use
* \return The image in compressed form */
QByteArray to_byte_array( Format format ) const
{ return saved_data.size() > 0 ? saved_data : format.to_byte_array( img ); }
{ return saved_data.size() > 0 ? saved_data : format.to_byte_array( remove_transparent() ); }

Image resize( int size ) const;

Expand All @@ -94,7 +90,7 @@ class Image {
QSize newSize( std::min( x+width, x+mask.width() ) - x
, std::min( y+height, y+mask.height() ) - y );
auto newMask = newSize.isNull() ? QImage() : mask.copy( x,y, newSize.width(), newSize.height() );
return Image( pos+QPoint(x,y), img.copy( x,y, newSize.width(), newSize.height() ), newMask );
return Image( img.copy( {x,y}, newSize ), newMask );
}

Image combine( Image on_top ) const;
Expand All @@ -108,25 +104,23 @@ class Image {
int compressed_size( Format format, Format::Precision p=Format::HIGH ) const;

int save_compressed_size( Format format ){
saved_data = remove_transparent().to_byte_array( format );
saved_data = to_byte_array( format );
return saved_data.size();
}

Image difference( Image img ) const;
Image remove_area( Image img ) const;
Image clean_alpha( int kernel_size, int threshold ) const;
Image remove_transparent() const;
Image discardTransparent() const;
QImage remove_transparent() const;
Image auto_crop() const;

Image optimize_filesize( Format format ) const;
int estimate_compressed_size( Format format ) const;

/** \param [in] other Image to compare against
* \return true if images are interchangeable */
bool operator==( const Image& other ) const{
return pos == other.pos && img == other.img;
}
bool operator==( const Image& other ) const
{ return img == other.img && mask == other.mask; }

bool mustKeepAlpha() const;
static Image fromTransparent( QImage img );
Expand Down
6 changes: 0 additions & 6 deletions src/MultiImage.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,6 @@ class MultiImage {
bool optimize2( QString name ) const;

bool validate( QString file ) const;

/** Remove alpha from input images */
void removeAlpha() { for( auto& original : originals ) original.removeAlpha(); }

/** Remove alpha from input images */
void discardTransparent() { for( auto& original : originals ) original = original.discardTransparent(); }
};

#endif
Expand Down
61 changes: 61 additions & 0 deletions src/SubQImage.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
This file is part of cgCompress.
cgCompress 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.
cgCompress 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 cgCompress. If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef SUB_QIMAGE_HPP
#define SUB_QIMAGE_HPP

#include <QImage>


/** Provides ReadOnly access to a region of a QImage without copying.
* row() and rowIndex() provides pixel access which is offset and casted correctly */
class SubQImage{
private:
QImage img;
QPoint pos;
QSize subsize;

auto scanLine( int iy ) const
{ return img.constScanLine( iy + pos.y() ); }

SubQImage( QImage img, QPoint pos, QSize subsize )
: img(img), pos(pos), subsize(subsize) {}
public:
SubQImage( QImage img, QPoint pos={0,0} )
: img(img), pos(pos), subsize(img.size()) { }

auto offset() const{ return pos; }

auto size() const{ return subsize; }
auto width() const{ return size().width(); }
auto height() const{ return size().height(); }

auto rowIndex( int iy ) const{ return scanLine( iy ) + pos.x(); }
auto row( int iy ) const{ return (const QRgb*)(scanLine( iy )) + pos.x(); }

auto get() const
{ return img.copy( pos.x(), pos.y(), width(), height() ); }

SubQImage copy( QPoint pos, QSize size ) const
{ return { img, offset() + pos, size }; }

bool operator==( const SubQImage& other ) const
{ return img == other.img && pos == other.pos && subsize == other.subsize; }
};

#endif

Loading

0 comments on commit c6adfe3

Please sign in to comment.