/* This file is a part of plaYUVer project * Copyright (C) 2014 by Luis Lucas (luisfrlucas@gmail.com) * Joao Carreira (jfmcarreira@gmail.com) * * 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. * * 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 for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ /** * \file ViewArea.cpp * \brief Function to manage image display * Based on the work of Ricardo N. Rocha Sardo in SCode project */ #include #include #include #include #include #include #include #include #include #include #include "ViewArea.h" #include "GridManager.h" #include "PlaYUVerFrame.h" #include "InputStream.h" namespace plaYUVer { static const QColor selectionColor = Qt::cyan; static const QColor imageMaskColor = Qt::green; static const QColor eraserColor = Qt::red; ViewArea::ViewArea( QWidget *parent ) : QWidget( parent ) { setAttribute( Qt::WA_StaticContents ); // setAttribute( Qt::WA_NoBackground ); setMouseTracking( true ); m_pixmap = QPixmap(); m_grid = GridManager(); m_mask = QBitmap(); m_selectedArea = QRect(); m_zoomFactor = 1; m_xOffset = 0; m_yOffset = 0; m_mode = NormalMode; m_tool = NavigationTool; m_gridVisible = false; m_snapToGrid = false; m_blockTrackEnable = false; m_pStream = NULL; } void ViewArea::setImage( const QPixmap &pixmap ) { m_pixmap = pixmap; m_mask = QBitmap( pixmap.width(), pixmap.height() ); m_mask.clear(); updateSize(); update(); updateGeometry(); } void ViewArea::clear() { m_pixmap = QPixmap(); m_mask = QBitmap(); m_mode = NormalMode; updateSize(); update(); updateGeometry(); } void ViewArea::clearMask() { m_mask.clear(); } Double ViewArea::getZoomFactor() { return m_zoomFactor; } // QSize ViewArea::sizeHint() const // { // int w = m_pixmap.width()*m_zoomFactor; // int h = m_pixmap.height()*m_zoomFactor; // return QSize( w, h ); // } Void ViewArea::zoomChangeEvent(Double factor) { Double zoomFactor; Double maxZoom = 100.0; Double minZoom = ( 32.0 / m_pixmap.width() ); if( ( m_zoomFactor == minZoom ) && (factor < 1 ) ) return; if( ( m_zoomFactor == maxZoom ) && (factor > 1 ) ) return; zoomFactor = m_zoomFactor * factor; if( zoomFactor < minZoom ) zoomFactor = minZoom; if( zoomFactor > maxZoom ) zoomFactor = maxZoom; setZoomFactor( zoomFactor ); emit zoomFactorChanged(factor); } void ViewArea::setZoomFactor( double f ) { m_zoomFactor = f; updateSize(); update(); } void ViewArea::setMode( ViewMode mode ) { if( m_mode == mode ) return; m_mode = mode; update(); } void ViewArea::setNormalMode() { setMode( NormalMode ); } void ViewArea::setMaskMode() { setMode( MaskMode ); } void ViewArea::setMaskColor( const QColor &color ) { m_maskColor = color; update(); } void ViewArea::setTool( Tool tool ) { m_tool = tool; } void ViewArea::setMaskTool() { setMode( MaskMode ); setTool( MaskTool ); m_blockTrackEnable = true; } void ViewArea::setEraserTool() { setMode( MaskMode ); setTool( EraserTool ); m_blockTrackEnable = true; } void ViewArea::setNormalSelectionTool() { setMode( NormalMode ); setTool( NormalSelectionTool ); m_blockTrackEnable = false; } void ViewArea::setBlockSelectionTool() { setMode( NormalMode ); setTool( BlockSelectionTool ); m_blockTrackEnable = true; } void ViewArea::setGridVisible( bool enable ) { m_gridVisible = enable; update(); } void ViewArea::setSnapToGrid( bool enable ) { m_snapToGrid = enable; } void ViewArea::setSelectedArea( QRect &rect ) { if( rect.isNull() ) { update(); return; } QRect updateRect; setNormalMode(); if( m_selectedArea.isNull() ) { m_selectedArea = rect.normalized(); updateRect = m_selectedArea; } else { updateRect = m_selectedArea; m_selectedArea = rect.normalized(); updateRect = updateRect.united( m_selectedArea ); } updateRect.adjust( 0, 0, 1, 1 ); update( updateRect.normalized() ); } //////////////////////////////////////////////////////////////////////////////// // Geometry Updates //////////////////////////////////////////////////////////////////////////////// void ViewArea::updateSize() { int w = m_pixmap.width() * m_zoomFactor; int h = m_pixmap.height() * m_zoomFactor; setMinimumSize( w, h ); updateOffset(); } void ViewArea::updateOffset() { if( width() > m_pixmap.width() * m_zoomFactor ) { m_xOffset = ( width() - m_pixmap.width() * m_zoomFactor ) / 2; } else { m_xOffset = 0; } if( height() > m_pixmap.height() * m_zoomFactor ) { m_yOffset = ( height() - m_pixmap.height() * m_zoomFactor ) / 2; } else { m_yOffset = 0; } } //////////////////////////////////////////////////////////////////////////////// // Resize Event //////////////////////////////////////////////////////////////////////////////// void ViewArea::resizeEvent( QResizeEvent *event ) { if( size().isEmpty() || m_pixmap.isNull() ) return; updateOffset(); update(); } //////////////////////////////////////////////////////////////////////////////// // Paint Event //////////////////////////////////////////////////////////////////////////////// void ViewArea::paintEvent( QPaintEvent *event ) { QRect winRect = event->rect(); if( size().isEmpty() || m_pixmap.isNull() ) return; QPainter painter( this ); // Save the actual painter properties and scales the coordinate system. painter.save(); painter.translate( m_xOffset, m_yOffset ); painter.scale( m_zoomFactor, m_zoomFactor ); // This line is for fast paiting. Only visible area of the image is painted. // We take the exposed rect from the event (that gives us scroll/expose optimizations for free – no need // to draw the whole pixmap if your widget is only partially exposed), and reverse map it with the painter matrix. // That gives us the part of the pixmap that has actually been exposed. // See: http://blog.qt.digia.com/blog/2006/05/13/fast-transformed-pixmapimage-drawing/ QRect exposedRect = painter.worldTransform().inverted() .mapRect(event->rect()) .adjusted(-1, -1, 1, 1); // Draw the pixmap. painter.drawPixmap( exposedRect, m_pixmap, exposedRect ); // Draw the Grid if it's visible. if( m_gridVisible ) { // Do we need to draw the whole grid? // To know that, we need to perform a transformation of the rectangle // area that the painter needs to update - transform the windows // coordinates (origin at the top-left corner of the widget), to the // relatives coordinates of the image at it's original size (origin at // the top-left corner of the image). QRect vr = windowToView( winRect ); // Now we have the (to update) rectangle area on a coordinates system // that has it's origin at the top-left corner of the image. That // is, is referenced to the not scaled image. // To know what image area we need to update, just intersects the // rectangle area with the image area. vr &= QRect( 0, 0, m_pixmap.width(), m_pixmap.height() ); // Set up for the grid drawer. painter.setRenderHint( QPainter::Antialiasing ); // Draw grid. m_grid.drawGrid( m_pixmap, vr, &painter ); } painter.restore(); // Draw a border around the image. if( m_xOffset || m_yOffset ) { painter.setPen( Qt::black ); painter.drawRect( m_xOffset - 1, m_yOffset - 1, m_pixmap.width() * m_zoomFactor + 1, m_pixmap.height() * m_zoomFactor + 1 ); } // Draw pixel values in grid if(m_zoomFactor>=25.0) { Int imageWidth = m_pixmap.width(); Int imageHeight = m_pixmap.height(); PlaYUVerFrame *curFrame = getInputStream()->getCurrFrame(); Pixel sPixelValue; QFont font("Helvetica"); font.setPixelSize(m_zoomFactor/4); painter.setFont(font); QRect vr = windowToView( winRect ); vr &= QRect( 0, 0, imageWidth, imageHeight ); for(Int i=vr.x() ; i<=vr.right() ; i++) { for(Int j=vr.y() ; j<=vr.bottom() ; j++) { QPoint pixelTopLeft(i,j); sPixelValue = curFrame->getPixelValue( pixelTopLeft, COLOR_YUV ); QRect pixelRect(viewToWindow(pixelTopLeft), QSize(m_zoomFactor,m_zoomFactor)); if( sPixelValue.Luma < 128 ) painter.setPen(QColor(Qt::white)); else painter.setPen(QColor(Qt::black)); painter.drawText(pixelRect, Qt::AlignCenter, QString::number(sPixelValue.Luma)); } } QColor color( Qt::white ); QPen mainPen = QPen( color, 1, Qt::SolidLine ); painter.setPen(mainPen); // Draw vertical line for( Int x = vr.x(); x <= (vr.right()+1); x ++ ) { // Always draw the full line otherwise the line stippling // varies with the location of view area and we get glitchy // patterns. painter.drawLine( viewToWindow(QPoint(x,0)), viewToWindow(QPoint(x,imageHeight)) ); } // Draw horizontal line for( Int y = vr.y(); y <= (vr.bottom()+1); y ++ ) { painter.drawLine( viewToWindow(QPoint(0,y)), viewToWindow(QPoint(imageWidth,y)) ); } } QRect sr = viewToWindow( m_selectedArea ); QRect ir = sr & winRect; if( ( mode() == NormalMode ) && !selectedArea().isNull() ) { if( m_newShape || m_blockTrackEnable ) { // Set the tool color QColor selectColor = selectionColor; selectColor.setAlpha( 120 ); QBrush brush( selectColor ); if( m_blockTrackEnable ) painter.setBrush( Qt::NoBrush ); else painter.setBrush( brush ); painter.setPen( Qt::darkCyan ); // painter.drawRect( sr/*m_selectedArea*/ ); } else { // 1) Cover the image with a light gray except the selected area QColor fill( Qt::lightGray ); fill.setAlpha( 150 ); QBrush brush( fill ); painter.setBrush( fill ); painter.setPen( Qt::NoPen ); QPainterPath myPath; QRect imgr = viewToWindow( QRect( 0, 0, m_pixmap.width(), m_pixmap.height() ) ); myPath.addRect( imgr ); myPath.addRect( sr/*m_selectedArea*/); painter.drawPath( myPath ); // 2) Draw the selection rectangle painter.setBrush( Qt::NoBrush ); painter.setPen( Qt::darkCyan ); // painter.drawRect( sr/*m_selectedArea*/ ); } if( !ir.isNull() ) painter.drawRect( sr/*m_selectedArea*/); } // Mask Mode ---------------------------------------------------------- if( mode() != MaskMode ) return; QColor color; QBrush brush; // Draw Mask // Set the tool color if( m_maskColor.isValid() ) color = m_maskColor; else color = imageMaskColor; color.setAlpha( 120 ); painter.setPen( color ); painter.save(); painter.translate( m_xOffset, m_yOffset ); painter.scale( m_zoomFactor, m_zoomFactor ); painter.drawPixmap( QPoint( 0, 0 ), m_mask ); painter.restore(); if( !m_selectedArea.isNull() ) { if( m_newShape || m_blockTrackEnable ) { switch( tool() ) { case MaskTool: { if( m_maskColor.isValid() ) color = m_maskColor; else color = imageMaskColor; break; } case EraserTool: { color = eraserColor; break; } default: color = selectionColor; // ?Problems! } color.setAlpha( 120 ); brush = QBrush( color ); if( m_blockTrackEnable ) painter.setBrush( Qt::NoBrush ); else painter.setBrush( brush ); painter.setPen( color ); if( !ir.isNull() ) painter.drawRect( sr ); } } } //////////////////////////////////////////////////////////////////////////////// // Mouse Events //////////////////////////////////////////////////////////////////////////////// void ViewArea::wheelEvent( QWheelEvent *event ) { Double factor; factor = 0.001 * event->delta(); if(factor>0) factor=1.25; else factor=0.8; zoomChangeEvent( factor ); } void ViewArea::mousePressEvent( QMouseEvent *event ) { event->accept(); QPoint vpos = windowToView( event->pos() ); if( event->button() == Qt::LeftButton ) { if( tool() == NavigationTool ) { m_lastWindowPos = event->pos(); } // Is the mouse over the image? If yes, save the mouse position; // otherwise, QPoint( -1, -1 ) indicates an invalid position. if( isPosValid( vpos ) ) { m_lastPos = vpos; } else { m_lastPos = QPoint( -1, -1 ); return; } // If grid tracking if( m_snapToGrid ) { // Find if the cursor is near a grid intersection bool isNear = m_grid.isNear( m_lastPos ); if( isNear ) { // The grid 'near intersection' found when used isNear() m_lastPos = m_grid.nearPos(); } } m_newShape = true; if( tool() == NormalSelectionTool ) { m_blockTrackEnable = false; return; } if( mode() == MaskMode ) { // Disable the block track to enable fill the block m_blockTrackEnable = false; } // Block selection // m_selectedArea = m_grid.rectContains( m_lastPos ); QRect updateRect = viewToWindow( m_selectedArea ); updateRect.adjust( 0, 0, 1, 1 ); update( updateRect.normalized() ); } } void ViewArea::mouseMoveEvent( QMouseEvent *event ) { event->accept(); QPoint actualPos = windowToView( event->pos() ); QRect updateRect; emit positionChanged( actualPos , getInputStream() ); // If mouse left button pressed if( event->buttons() == Qt::LeftButton && m_lastPos != QPoint( -1, -1 ) ) { // Grid tracking if( m_snapToGrid ) { // Find if the cursor is near a grid intersection bool isNear = m_grid.isNear( actualPos ); if( isNear ) { // Return the last grid near intersection found actualPos = m_grid.nearPos(); } } if( tool() == NavigationTool ) { QPoint offset = m_lastWindowPos - event->pos() ; emit moveScroll( offset ); } updateRect = viewToWindow( m_selectedArea ); if( tool() == NormalSelectionTool ) { // If the selection is only vertical or horizontal then we have one // of the dimentions null. if( actualPos.x() == m_lastPos.x() || actualPos.y() == m_lastPos.y() ) { m_selectedArea = QRect(); } // Selection from top to bottom else if( m_lastPos.y() < actualPos.y() ) { // From left to right if( m_lastPos.x() < actualPos.x() ) { QPoint bottomR = actualPos - QPoint( 1, 1 ); m_selectedArea = QRect( m_lastPos, bottomR ); } // From right to left else { QPoint topL( actualPos.x(), m_lastPos.y() ); QPoint bottomR( m_lastPos.x() - 1, actualPos.y() - 1 ); m_selectedArea = QRect( topL, bottomR ); } } // Selection from bottom to top else { // From left to right if( m_lastPos.x() < actualPos.x() ) { QPoint topL( m_lastPos.x(), actualPos.y() ); QPoint bottomR( actualPos.x() - 1, m_lastPos.y() - 1 ); m_selectedArea = QRect( topL, bottomR ); } // From right to left else { QPoint bottomR = m_lastPos - QPoint( 1, 1 ); m_selectedArea = QRect( actualPos, bottomR ); } } } /* else // if tool() == BlockSelectionTool || MaskTool || EraserTool { m_blockTrackEnable = false; // If cursor is inside the selected area, we most redraw // the selection rect because it may be smaller. if( m_selectedArea.contains( actualPos ) ) { m_selectedArea = m_grid.rectContains( m_lastPos ); } m_selectedArea = m_selectedArea.united( m_grid.rectContains( actualPos ) ); } */ if( tool() == NormalSelectionTool ) { // Intercept the selected area with the image area to limit the // selection only to the image area, preventing it to come outside // the image. m_selectedArea &= QRect( 0, 0, m_pixmap.width(), m_pixmap.height() ); // Update only the united area updateRect = updateRect.united( viewToWindow( m_selectedArea ) ); // "When rendering with a one pixel wide pen the QRect's // boundary line will be rendered to the right and below the // mathematical rectangle's boundary line.", in QT4 doc. // Our selection pen width is 1, let's adjust the rendering area. updateRect.adjust( 0, 0, 1, 1 ); update( updateRect.normalized() ); } return; } // If no mouse button is pressed and bloks selection tool is set, then // track the bloks. if( m_blockTrackEnable ) { updateRect = viewToWindow( m_selectedArea ); if( isPosValid( actualPos ) ) { m_selectedArea = m_grid.rectContains( actualPos ); } else { m_selectedArea = QRect(); } updateRect = updateRect.united( viewToWindow( m_selectedArea ) ); updateRect.adjust( 0, 0, 1, 1 ); update( updateRect ); return; } } void ViewArea::mouseReleaseEvent( QMouseEvent *event ) { event->accept(); QPoint vpos = windowToView( event->pos() ); if( event->button() == Qt::LeftButton && m_lastPos != QPoint( -1, -1 ) ) { // Normal Mode ------------------------------------------------------ if( mode() == NormalMode ) { if( vpos == m_lastPos ) { m_selectedArea = QRect(); if( tool() == BlockSelectionTool ) m_blockTrackEnable = true; } emit selectionChanged( m_selectedArea ); } // Mask Mode ----------------------------------------------------------- else if( mode() == MaskMode ) { if( !m_selectedArea.isNull() ) { updateMask( m_selectedArea ); } m_selectedArea = QRect(); m_blockTrackEnable = true; } m_newShape = false; update(); unsetCursor(); m_lastPos = QPoint( -1, -1 ); } } //////////////////////////////////////////////////////////////////////////////// bool ViewArea::isPosValid( const QPoint &pos ) const { if( pos.x() < 0 || pos.y() < 0 || pos.x() >= m_pixmap.width() || pos.y() >= m_pixmap.height() ) return false; else return true; } //////////////////////////////////////////////////////////////////////////////// QPoint ViewArea::windowToView( const QPoint& pt ) const { QPoint p; p.setX( static_cast( ( pt.x() - m_xOffset ) / m_zoomFactor ) ); p.setY( static_cast( ( pt.y() - m_yOffset ) / m_zoomFactor ) ); return p; } QRect ViewArea::windowToView( const QRect& rc ) const { QRect r; r.setTopLeft( windowToView( rc.topLeft() ) ); // r.setRight ( (int)( ceil(( rc.right() - m_xOffset)/m_zoomFactor ))); // r.setBottom( (int)( ceil(( rc.bottom()- m_yOffset)/m_zoomFactor ))); // r.setRight ( static_cast(( rc.right() - m_xOffset ) / m_zoomFactor +1)); // r.setBottom( static_cast(( rc.bottom() - m_xOffset ) / m_zoomFactor+1)); r.setBottomRight( windowToView( rc.bottomRight() ) ); return r; } QPoint ViewArea::viewToWindow( const QPoint& pt ) const { QPoint p; p.setX( static_cast( pt.x() * m_zoomFactor + m_xOffset ) ); p.setY( static_cast( pt.y() * m_zoomFactor + m_yOffset ) ); return p; } QRect ViewArea::viewToWindow( const QRect& rc ) const { QRect r; r.setTopLeft( viewToWindow( rc.topLeft() ) ); // r.setRight ( (int)( ceil(( rc.right() +1+m_xOffset )*m_zoomFactor ) - 1 )); // r.setBottom( (int)( ceil(( rc.bottom()+1+m_yOffset )*m_zoomFactor ) - 1 )); // r.setRight ( (int)( ceil(( rc.right()+0.5)*m_zoomFactor )+ m_xOffset )-1); // r.setBottom( (int)( ceil(( rc.bottom()+0.5)*m_zoomFactor ) +m_yOffset )-1); // qDebug()<<"Right = "<< r.right(); // r.setRight ( static_cast(( rc.right()+1) * m_zoomFactor + m_xOffset -1) ); // r.setBottom( static_cast(( rc.bottom()+1) * m_zoomFactor + m_yOffset -1)); r.setBottomRight( viewToWindow( rc.bottomRight() ) ); return r; } //////////////////////////////////////////////////////////////////////////////// // Masks Management //////////////////////////////////////////////////////////////////////////////// void ViewArea::updateMask( const QRect &rect ) { switch( tool() ) { case MaskTool: { // Add rect to the mask QPainter painter( &m_mask ); painter.setBrush( Qt::color1 ); painter.setPen( Qt::NoPen ); painter.drawRect( rect ); painter.end(); break; } case EraserTool: { // Clears rect area in the mask QPainter painter( &m_mask ); painter.setBrush( Qt::color0 ); painter.setPen( Qt::NoPen ); painter.drawRect( rect ); painter.end(); break; } default: /* Do Nothing */ ; } } void ViewArea::setInputStream( InputStream *stream ) { m_pStream = stream; } InputStream* ViewArea::getInputStream() { return m_pStream; } } // NameSpace plaYUVer