Logo Search packages:      
Sourcecode: kmess version File versions

inkedit.cpp

/***************************************************************************
                          inkvedit.cpp -  description
                             -------------------
    begin                : Wed May 14 2008
    copyright            : (C) 2008 by Antonio Nastasi
                           (C) 2009 by Sjors Gielen
    email                : sifcenter@gmail.com
                           dazjorz@kmess.org
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "inkedit.h"

#include "../kmessdebug.h"
#include "config-kmess.h"

#if KMESS_ENABLE_INK == 1
  #include <gif_lib.h>
#endif

#include <KIcon>
#include <KColorDialog>

#include <QMouseEvent>
#include <QPainter>
#include <QPen>
#include <QTemporaryFile>



#ifdef KMESSDEBUG_INKEDIT
  #define KMESSDEBUG_INKEDIT_GENERAL
#endif



// Constructor
InkEdit::InkEdit( QWidget *parent )
: QWidget( parent )
, Ui::InkEdit()
, color_( Qt::black )
, erasingImage_( false )
, image_( 0 )
, isEmpty_( true )
, scribbling_( false )
{
  setupUi( this );
  setAttribute( Qt::WA_StaticContents );

  // Set the buttons
  colorButton_->setIcon( KIcon( "format-stroke-color" ) );
  eraseButton_->setIcon( KIcon( "draw-eraser"         ) );
  clearButton_->setIcon( KIcon( "edit-clear"          ) );

  connect( colorButton_, SIGNAL(     clicked() ),
           this,         SLOT  ( changeColor() ) );
  connect( eraseButton_, SIGNAL(     clicked() ),
           this,         SLOT  (  eraseBrush() ) );
  connect( clearButton_, SIGNAL(     clicked() ),
           this,         SLOT  (  clearImage() ) );
}



// Clear the current image
void InkEdit::clearImage()
{
  if( image_ == 0 )
  {
    kWarning() << "Uninitialized usage of InkEdit!";
    return;
  }

  image_->pixels.fill( qRgb( 255, 255, 255 ) );
  update();
  isEmpty_ = true;

  // Reset the drawn area
  image_->area = QRect();

  emit inkChanged();
}



// Change color for current pen
void InkEdit::changeColor()
{
  KColorDialog::getColor( color_ );
}



// Crop the image to send only the drawed area
void InkEdit::cropImage( QPoint position )
{
  if( image_ == 0 )
  {
    kWarning() << "Uninitialized usage of InkEdit!";
    return;
  }

  int sizePen = sizePen_->value();

  int minX = position.x() - sizePen;
  int maxX = position.x() + sizePen;
  int minY = position.y() - sizePen;
  int maxY = position.y() + sizePen;

  // Fix min and max to never spread larger than the image
  // First fix max to not be bigger than the image, then fix min, then max again
  maxX = qBound( 0,      maxX, image_->pixels.width() );
  minX = qBound( 0,      minX, maxX );
  maxX = qBound( minX,   maxX, maxX );
  // Same for Y
  maxY = qBound( 0,      maxY, image_->pixels.height() );
  minY = qBound( 0,      minY, maxY );
  maxY = qBound( minY,   maxY, maxY );

  // Adjust the initial size and position
  if( image_->area.right() == -1 || image_->area.bottom() == -1 )
  {
    image_->area.setLeft  ( minX );
    image_->area.setRight ( maxX );
    image_->area.setTop   ( minY );
    image_->area.setBottom( maxY );
    return;
  }

  // Adjust the x position and width
  if( minX < image_->area.left() )
  {
    image_->area.setLeft( minX );
  }
  if( maxX > image_->area.right() )
  {
    image_->area.setRight( maxX );
  }

  // Adjust the y position and height
  if( minY < image_->area.top() )
  {
    image_->area.setTop( minY );
  }
  if( maxY > image_->area.bottom() )
  {
    image_->area.setBottom( maxY );
  }
}



// Draw a line from start point to last point
void InkEdit::drawLineTo( const QPoint &endPoint )
{
  if( image_ == 0 )
  {
    kWarning() << "Uninitialized usage of InkEdit!";
    return;
  }

  QPainter painter( &(image_->pixels) );
  painter.setRenderHints( QPainter::Antialiasing | QPainter::SmoothPixmapTransform, true );

  QColor color = color_;
  if( erasingImage_ )
  {
    color = QColor( "white" );
  }

  painter.setPen( QPen( color, sizePen_->value(), Qt::SolidLine, Qt::RoundCap,
                  Qt::RoundJoin ) );

  painter.drawLine( lastPoint_, endPoint );

  int rad = (1 / 2) + 2;
  update( QRect( lastPoint_, endPoint ).normalized()
          .adjusted( -rad, -rad, +rad, +rad ) );

  lastPoint_ = endPoint;
}



// Erase brush was selected
void InkEdit::eraseBrush()
{
  erasingImage_ = ! erasingImage_;

  if( erasingImage_ )
  {
    eraseButton_->setIcon( KIcon( "draw-freehand" ) );
  }
  else
  {
    eraseButton_->setIcon( KIcon( "draw-eraser" ) );
  }
}



// Returns true if the image is empty
bool InkEdit::isEmpty()
{
  return isEmpty_;
}



void InkEdit::mousePressEvent( QMouseEvent *event )
{
  if( image_ == 0 )
  {
    kWarning() << "Uninitialized usage of InkEdit!";
    return;
  }

  if( event->button() == Qt::LeftButton )
  {
    lastPoint_ = event->pos();
    cropImage( lastPoint_ );
    scribbling_ = true;
  }
}



void InkEdit::mouseMoveEvent(QMouseEvent *event)
{
  if( image_ == 0 )
  {
    kWarning() << "Uninitialized usage of InkEdit!";
    return;
  }

  if( ( event->buttons() & Qt::LeftButton ) && scribbling_ )
  {
    QPoint position = event->pos();
    cropImage(  position );
    drawLineTo( position );
  }
}



void InkEdit::mouseReleaseEvent(QMouseEvent *event)
{
  if( image_ == 0 )
  {
    kWarning() << "Uninitialized usage of InkEdit!";
    return;
  }

  if( event->button() == Qt::LeftButton && scribbling_ )
  {
    QPoint position = event->pos();
    cropImage(  position );
    drawLineTo( position );

    isEmpty_ = false;
    scribbling_ = false;
    emit inkChanged();
  }
}



void InkEdit::paintEvent(QPaintEvent *event)
{
  Q_UNUSED( event );

  if( image_ == 0 || image_->pixels.isNull() )
  {
    kWarning() << "Uninitialized usage of InkEdit!";
    return;
  }

  QPainter painter( this );
  painter.drawImage( QPoint(0, 0), image_->pixels );
}



void InkEdit::resizeEvent( QResizeEvent *event )
{
  if( image_ == 0 )
  {
    kWarning() << "Uninitialized usage of InkEdit!";
    return;
  }

  if( width() > image_->pixels.width() || height() > image_->pixels.height() )
  {
    int newWidth = qMax( width() + 128, image_->pixels.width() );
    int newHeight = qMax( height() + 128, image_->pixels.height() );
    resizeImage( &(image_->pixels), QSize( newWidth, newHeight ) );
    update();
  }

  QWidget::resizeEvent( event );
}



// Return the image
InkImage *InkEdit::getImage()
{
  return image_;
}



// Return the bytes representing the image
const QByteArray& InkEdit::getImageBytes()
{
  if( image_ == 0 )
  {
    kWarning() << "Uninitialized usage of InkEdit!";
    imageBytes_.clear();
    return imageBytes_;
  }

#if KMESS_ENABLE_INK == 1
  // Copy the interesting piece of the image over to a new image
  QImage image = image_->pixels.copy( image_->area ).convertToFormat( QImage::Format_Indexed8, Qt::ThresholdDither );

  // Initialise the gif variables
  GifFileType *gft     = NULL;
  ColorMapObject *cmap = NULL;
  int height           = image.height();
  int width            = image.width();
  bool gifError        = false;

  // Open a temporary file
  QTemporaryFile tempFile( "kmess-ink-XXXXXX.gif" );
  QFile tempFile2;
  if( ! tempFile.open() )
  {
    kWarning() << "Couldn't open temporary file for GIF conversion.";
    goto error;
  }

#ifdef KMESSDEBUG_INKEDIT_GENERAL
  kDebug() << "Created temporary file to convert ink to GIF image.";
#endif

  // Convert the image to GIF using libgif
  // This is needed because Windows Live Messenger (at least 2009) does not display inks
  // which are not in GIF format (it doesn't even give an error).
  gifError             = true;

  // Open the gif file
  gft = EGifOpenFileHandle( tempFile.handle() );
  if( gft == 0 )
  {
    kWarning() << "Couldn't initialize gif library.";
    goto error;
  }

  // Create the color map
  cmap = MakeMapObject( image.numColors(), NULL );
  if( cmap == 0 )
  {
    kWarning() << "Couldn't create map object for gif conversion (num colors = " << image.numColors() << ")";
    goto error;
  }

  // Fill in the color map with the colors in the image color table
  for( int i = 0; i < image.numColors(); i++ )
  {
    QRgb color = image.color( i );
    cmap->Colors[i].Red = qRed( color );
    cmap->Colors[i].Green = qGreen( color );
    cmap->Colors[i].Blue = qBlue( color );
  }

  // Initialize the GIF file
  if( EGifPutScreenDesc( gft, width, height, 8, 0, cmap) == GIF_ERROR )
  {
    kWarning() << "EGifPutScreenDesc failed.";
    goto error;
  }

  // Initialize the GIF image
  if( EGifPutImageDesc( gft, 0, 0, width, height, 0, NULL) == GIF_ERROR )
  {
    kWarning() << "EGifPutImageDesc failed.";
    goto error;
  }

  // Write every line
  for( int j = 0; j < height; j++ )
  {
    // FIXME: This is the point where the artifacts are made. I don't know why, but if you run:
    //  EGifPutLine( gft, image.bits(), image.width() * image.height() ) // i.e. convert the complete image in one call
    // then the image is mangled, so something is wrong with the width or so, it seems to be off by two pixels or so. (Try it!)
    //if( EGifPutLine( gft, image.scanLine( j ), width ) == GIF_ERROR )
    if( EGifPutLine( gft, image.scanLine( j ), width ) == GIF_ERROR )
    {
      kWarning() << "EGifPutLine failed for scanline" << j << "(height=" << image.height() << ";width=" << image.width() << ";bytesperline=" << image.bytesPerLine() << ")";
      goto error;
    }
  }

  // Clean up the GIF converter etc
  EGifCloseFile( gft );
  FreeMapObject( cmap );
  gifError = false;

  // Read the file back in
  // Because the QTemporaryFile is opened in unbuffered mode, we have to flush it then re-open it with QFile.
  tempFile.flush();
  tempFile2.setFileName( tempFile.fileName() );
  if( ! tempFile2.open( QIODevice::ReadOnly | QIODevice::Unbuffered ) )
  {
    kWarning() << "Couldn't reopen temporary file: " << tempFile2.errorString();
    goto error;
  }

  imageBytes_ = tempFile2.readAll();

  tempFile2.close();
  tempFile.close();

#ifdef KMESSDEBUG_INKEDIT_GENERAL
  kDebug() << "Converted ink to GIF (" << image.height() << "x" << image.width() << "), sending " << imageBytes_.size() << " bytes of image data.";
#endif

  return imageBytes_;

error:
  if( gifError )
  {
    kWarning() << "A GIF error occured in getImageBytes, returning empty byte array. The error was: ";
    PrintGifError();
  }
  else
  {
    kWarning() << "A non-GIF error occured in getImageBytes, returning empty byte array.";
  }

  imageBytes_.clear();
  return imageBytes_;
#else // KMESS_ENABLE_INK
  imageBytes_.clear();
  return imageBytes_;
#endif // KMESS_ENABLE_INK
}



void InkEdit::resizeImage( QImage *image, const QSize &newSize )
{
  if( image_ == 0 )
  {
    kWarning() << "Uninitialized usage of InkEdit!";
    return;
  }

  if( image->size() == newSize )
  {
    return;
  }

  QImage newImage( newSize, QImage::Format_ARGB32 );
  newImage.fill( qRgb( 255, 255, 255 ) );
  QPainter painter( &newImage );
  if( ! image->isNull() )
  {
    painter.drawImage( QPoint( 0, 0 ), (*image).scaled( newSize, Qt::KeepAspectRatio, Qt::SmoothTransformation ) );
  }
  else
  {
    painter.drawImage( QPoint( 0, 0 ), *image );
  }

  *image = newImage;
}



// Change the image
void InkEdit::setImage( InkImage *newImage )
{
  image_ = newImage;
  update();

  if( image_ )
  {
    // Initialize the image if needed
    if( ! image_->initialized )
    {
      resizeImage( &(image_->pixels), size() );
      clearImage();
      image_->initialized = true;
    }

    isEmpty_ = image_->area.isNull();
  }
  else
  {
    isEmpty_ = true;
  }
}



#include "inkedit.moc"

Generated by  Doxygen 1.6.0   Back to index