Пример #1
0
void GLExtras::drawFindOnPage(SkRect& viewport)
{
    WTF::Vector<MatchInfo>* matches = m_findOnPage->matches();
    XLOG("drawFindOnPage, matches: %p", matches);
    if (!matches || !m_findOnPage->isCurrentLocationValid())
        return;
    int count = matches->size();
    int current = m_findOnPage->currentMatchIndex();
    XLOG("match count: %d", count);
    if (count < MAX_NUMBER_OF_MATCHES_TO_DRAW)
        for (int i = 0; i < count; i++) {
            MatchInfo& info = matches->at(i);
            const SkRegion& region = info.getLocation();
            SkIRect rect = region.getBounds();
            if (rect.intersect(viewport.fLeft, viewport.fTop,
                               viewport.fRight, viewport.fBottom))
                drawRegion(region, i == current, false, true);
#ifdef DEBUG
            else
                XLOG("Quick rejecting [%dx%d, %d, %d", rect.fLeft, rect.fTop,
                     rect.width(), rect.height());
#endif // DEBUG
        }
    else {
        MatchInfo& info = matches->at(current);
        drawRegion(info.getLocation(), true, false, true);
    }
}
Пример #2
0
void QgsGrassRegionEdit::setRegion( const QgsPoint& ul, const QgsPoint& lr )
{
  mStartPoint = ul;
  mEndPoint = lr;
  calcSrcRegion();
  drawRegion( canvas(), mRubberBand, mSrcRectangle, mCoordinateTransform, true );
  drawRegion( canvas(), mSrcRubberBand, QgsRectangle( mStartPoint, mEndPoint ), QgsCoordinateTransform(), true );
}
Пример #3
0
void Text::draw(Canvas*, const Allocation&) const 
{
	canvas_->push_clipping();
	canvas_->clip_rect(allocation_->left(), allocation_->bottom(),
		allocation_->right(), allocation_->top());

	FontBoundingBox fbb;
	font_->font_bbox(fbb);
	Coord r = curLowerY_ / (fbb.ascent() + fbb.descent());
	unsigned i = unsigned(r);
	Coord y = allocation_->top() + (r - i) * (fbb.ascent() + fbb.descent());
	unsigned max = Math::max(Math::max(selection_.line2(), insertion_.line_),
		(unsigned) text_->Height() ? (unsigned) text_->Height() - 1 : 0);
	for (; i <= max; ++i) 
	{
		y -= fbb.ascent();

		if (damaged(i)) 
		{
			Coord x = allocation_->left() - curLowerX_;
			if (i < text_->Height()) 
			{
				const String line = text_->getNth(i);
				drawRegion(selection_, i, x, y, line);
				if (! readOnly_) 
				{
					drawLocation(insertion_, i, x, y, line);
				}
				for (GlyphIndex j = 0; j < annotation_.count(); ++j) 
				{
					drawRegion(*annotation_.item(j), i, x, y, line);
				}
				drawLine(i, x, y, line);
			} 
			else 
			{
				String line;
				drawRegion(selection_, i, x, y, line);
				if (! readOnly_) 
				{
					drawLocation(insertion_, i, x, y, line);
				}
				for (GlyphIndex j = 0; j < annotation_.count(); ++j) 
				{
					drawRegion(*annotation_.item(j), i, x, y, line);
				}
				drawLine(i, x, y, line);
			}
		}
  
		y -= fbb.descent();
		if (y < (allocation_->bottom() - fbb.ascent())) 
			break;
	}
	canvas_->pop_clipping();
}
Пример #4
0
void QgsGrassNewMapset::regionChanged()
{

  mRegionModified = true;
  checkRegion();
  drawRegion();
}
Пример #5
0
void TiledCanvas::paint(QPainter *p, const QStyleOptionGraphicsItem *, QWidget *)
{
    if (m_context2d->size() != m_tileSize)
        m_context2d->setSize(m_tileSize);

    const int tw = m_tileSize.width();
    const int th = m_tileSize.height();

    int h1 = m_canvasWindow.left() / tw;
    int htiles = ((m_canvasWindow.right() - h1 * tw) + tw - 1) / tw;

    int v1 = m_canvasWindow.top() / th;
    int vtiles = ((m_canvasWindow.bottom() - v1 * th) + th - 1) / th;

    for (int yy = 0; yy < vtiles; ++yy) {
        for (int xx = 0; xx < htiles; ++xx) {
            int ht = xx + h1;
            int vt = yy + v1;

            m_context2d->reset();
            m_context2d->setPainterTranslate(QPoint(-ht * tw, -vt * th));

            emit drawRegion(m_context2d, QRect(ht * tw, vt * th, tw, th));

            p->drawPixmap(-m_canvasWindow.x() + ht * tw, -m_canvasWindow.y() + vt * th, m_context2d->pixmap());
        }
    }
}
Пример #6
0
void QgsGrassNewMapset::regionChanged()
{
    QgsDebugMsg( "entered." );

    mRegionModified = true;
    checkRegion();
    drawRegion();
}
Пример #7
0
void QgsGrassNewMapset::setCurrentRegion()
{
    QgsDebugMsg( "entered." );

    QgsRectangle ext = mIface->mapCanvas()->extent();

    int srsid = QgsProject::instance()->readNumEntry(
                    "SpatialRefSys", "/ProjectCRSID", 0 );

    QgsCoordinateReferenceSystem srs( srsid, QgsCoordinateReferenceSystem::InternalCrsId );
    QgsDebugMsg( QString( "current project srsid = %1" ).arg( srsid ) );
    QgsDebugMsg( QString( "srs.isValid() = %1" ).arg( srs.isValid() ) );

    std::vector<QgsPoint> points;

    // TODO: this is not perfect
    points.push_back( QgsPoint( ext.xMinimum(), ext.yMinimum() ) );
    points.push_back( QgsPoint( ext.xMaximum(), ext.yMaximum() ) );

    // TODO add a method, this code is copy-paste from setSelectedRegion
    if ( srs.isValid() && mSrs.isValid()
            && srs.srsid() != mSrs.srsid() )
    {
        QgsCoordinateTransform trans( srs, mSrs );

        bool ok = true;
        for ( int i = 0; i < 2; i++ )
        {
            try
            {
                points[i] = trans.transform( points[i] );
            }
            catch ( QgsCsException &cse )
            {
                Q_UNUSED( cse );
                QgsDebugMsg( "Cannot transform point" );
                ok = false;
                break;
            }
        }

        if ( !ok )
        {
            QMessageBox::warning( 0, tr( "Warning" ), tr( "Cannot reproject region" ) );
            return;
        }
    }
    mNorthLineEdit->setText( QString::number( points[1].y() ) );
    mSouthLineEdit->setText( QString::number( points[0].y() ) );
    mEastLineEdit->setText( QString::number( points[1].x() ) );
    mWestLineEdit->setText( QString::number( points[0].x() ) );

    mRegionModified = true;
    checkRegion();
    drawRegion();
    QgsDebugMsg( "setCurrentRegion - End" );
}
Пример #8
0
void QgsGrassNewMapset::setCurrentRegion()
{
  QgsDebugMsg( "entered" );

  QgsRectangle ext = mIface->mapCanvas()->extent();

  QgsCoordinateReferenceSystem srs = mIface->mapCanvas()->mapSettings().destinationCrs();
  QgsDebugMsg( "srs = " + srs.toWkt() );

  std::vector<QgsPoint> points;

  // TODO: this is not perfect
  points.push_back( QgsPoint( ext.xMinimum(), ext.yMinimum() ) );
  points.push_back( QgsPoint( ext.xMaximum(), ext.yMaximum() ) );

  // TODO add a method, this code is copy-paste from setSelectedRegion
  if ( srs.isValid() && mCrs.isValid()
       && srs.srsid() != mCrs.srsid() )
  {
    QgsCoordinateTransform trans( srs, mCrs );

    bool ok = true;
    for ( int i = 0; i < 2; i++ )
    {
      try
      {
        points[i] = trans.transform( points[i] );
      }
      catch ( QgsCsException &cse )
      {
        Q_UNUSED( cse );
        QgsDebugMsg( "Cannot transform point" );
        ok = false;
        break;
      }
    }

    if ( !ok )
    {
      QgsGrass::warning( tr( "Cannot reproject region" ) );
      return;
    }
  }
  mNorthLineEdit->setText( QString::number( points[1].y() ) );
  mSouthLineEdit->setText( QString::number( points[0].y() ) );
  mEastLineEdit->setText( QString::number( points[1].x() ) );
  mWestLineEdit->setText( QString::number( points[0].x() ) );

  mRegionModified = true;
  checkRegion();
  drawRegion();
  QgsDebugMsg( "setCurrentRegion - End" );
}
Пример #9
0
void GLExtras::drawCursorRings()
{
    SkRegion region;
    for (size_t i = 0; i < m_ring->rings().size(); i++) {
        IntRect rect = m_ring->rings().at(i);
        if (i == 0)
            region.setRect(rect);
        else
            region.op(rect, SkRegion::kUnion_Op);
    }
    drawRegion(region, m_ring->m_isPressed, !m_ring->m_isButton, false);
}
Пример #10
0
void sceGuDispBuffer(int width, int height, void* dispbp, int dispbw)
{
	gu_draw_buffer.width = width;
	gu_draw_buffer.height = height;
	gu_draw_buffer.disp_buffer = dispbp;

	if (!gu_draw_buffer.frame_width || (gu_draw_buffer.frame_width != dispbw))
		gu_draw_buffer.frame_width = dispbw;

	drawRegion(0,0,gu_draw_buffer.width,gu_draw_buffer.height);
	sceDisplaySetMode(0,gu_draw_buffer.width,gu_draw_buffer.height);

	if (gu_display_on)
		sceDisplaySetFrameBuf((void*)(((unsigned int)ge_edram_address) + ((unsigned int)gu_draw_buffer.disp_buffer)), dispbw, gu_draw_buffer.pixel_size, PSP_DISPLAY_SETBUF_NEXTFRAME);
}
Пример #11
0
void QmlProfilerCanvas::paint(QPainter *p, const QStyleOptionGraphicsItem *, QWidget *)
{
    if (m_context2d->size().width() != width() || m_context2d->size().height() != height()) {
        m_dirty = true;
        m_context2d->setSize(width(), height());
    }

    if (m_dirty) {
        m_context2d->reset();

        emit drawRegion(m_context2d, QRect(0, 0, width(), height()));
        setDirty(false);
    }

    p->drawPixmap(0, 0, m_context2d->pixmap());
}
Пример #12
0
void QgsGrassNewMapset::setSelectedRegion()
{
    QgsDebugMsg( "entered." );

    // mRegionsPoints are in EPSG 4326 = LL WGS84
    int index = 2 * mRegionsComboBox->currentIndex();

    std::vector<QgsPoint> points;
    // corners ll lr ur ul
    points.push_back( QgsPoint( mRegionsPoints[index] ) );
    points.push_back( QgsPoint( mRegionsPoints[index+1].x(),
                                mRegionsPoints[index].y() ) );
    points.push_back( QgsPoint( mRegionsPoints[index+1] ) );
    points.push_back( QgsPoint( mRegionsPoints[index].x(),
                                mRegionsPoints[index+1].y() ) );

    // Convert to currently selected coordinate system


    // Warning: seems that crashes if source == dest
    if ( mProjectionSelector->selectedCrsId() != GEOCRS_ID )
    {
        // Warning: QgsCoordinateReferenceSystem::EpsgCrsId is broken (using epsg_id)
        //QgsCoordinateReferenceSystem source ( 4326, QgsCoordinateReferenceSystem::EpsgCrsId );
        QgsCoordinateReferenceSystem source( GEOCRS_ID, QgsCoordinateReferenceSystem::InternalCrsId );

        if ( !source.isValid() )
        {
            QMessageBox::warning( 0, tr( "Warning" ),
                                  tr( "Cannot create QgsCoordinateReferenceSystem" ) );
            return;
        }

        QgsCoordinateReferenceSystem dest( mProjectionSelector->selectedCrsId(),
                                           QgsCoordinateReferenceSystem::InternalCrsId );

        if ( !dest.isValid() )
        {
            QMessageBox::warning( 0, tr( "Warning" ),
                                  tr( "Cannot create QgsCoordinateReferenceSystem" ) );
            return;
        }

        QgsCoordinateTransform trans( source, dest );

        bool ok = true;
        for ( int i = 0; i < 4; i++ )
        {
            QgsDebugMsg( QString( "%1,%2->" ).arg( points[i].x() ).arg( points[i].y() ) );
            try
            {
                points[i] = trans.transform( points[i] );
                QgsDebugMsg( QString( "%1,%2" ).arg( points[i].x() ).arg( points[i].y() ) );
            }
            catch ( QgsCsException &cse )
            {
                Q_UNUSED( cse );
                QgsDebugMsg( "Cannot transform point" );
                ok = false;
                break;
            }
        }

        if ( !ok )
        {
            QMessageBox::warning( 0, tr( "Warning" ),
                                  tr( "Cannot reproject selected region." ) );
            return;
        }
    }

    double n = -90.0, s = 90.0, e = -180.0, w = 180.0;

    if ( mCellHead.proj == PROJECTION_LL )
    {
        n = points[2].y();
        s = points[0].y();
        e = points[1].x();
        w = points[0].x();

        if ( n > 90 )
            n = 90;
        if ( s < -90 )
            s = -90;
#if 0
        if ( e > 180 )
            e = 180;
        if ( w < -180 )
            w = 180;
#endif
    }
    else
    {
        for ( int i = 0; i < 4; i++ )
        {
            if ( i == 0 || points[i].y() > n )
                n = points[i].y();
            if ( i == 0 || points[i].y() < s )
                s = points[i].y();
            if ( i == 0 || points[i].x() > e )
                e = points[i].x();
            if ( i == 0 || points[i].x() < w )
                w = points[i].x();
        }
    }

    mNorthLineEdit->setText( QString::number( n ) );
    mSouthLineEdit->setText( QString::number( s ) );
    mEastLineEdit->setText( QString::number( e ) );
    mWestLineEdit->setText( QString::number( w ) );

    mRegionModified = true;
    checkRegion();
    drawRegion();
}
Пример #13
0
	void WidgetSkin::drawToData(int *data, int x, int y, int width, int height, eType type) {
		MAPoint2d dst;
		MAPoint2d dst2;
		MAHandle image;
		switch(type) {
			case SELECTED:
				image = selectedImage;
				break;
			case UNSELECTED:
				image = unselectedImage;
				break;
			default:
				maPanic(0, "WidgetSkin::draw undefined drawing type");
		}

		if(image == 0) return;

		// draw corners
		dst.x = x; dst.y = y;
		//maDrawImageRegion(image, &topLeft, &dst, TRANS_NONE);
		drawRegion(image, data, width, &topLeft, &dst);

		dst.x = x; dst.y = y+height-bottomLeft.height;
		//maDrawImageRegion(image, &bottomLeft, &dst, TRANS_NONE);
		drawRegion(image, data, width, &bottomLeft, &dst);

		dst.x = x+width-topRight.width; dst.y = y;
		//maDrawImageRegion(image, &topRight, &dst, TRANS_NONE);
		drawRegion(image, data, width, &topRight, &dst);

		dst.x = x+width-bottomRight.width; dst.y = y+height-bottomRight.height;
		//maDrawImageRegion(image, &bottomRight, &dst, TRANS_NONE);
		drawRegion(image, data, width, &bottomRight, &dst);

		// draw middle
		if(center.height && center.width) {
		for(int j = y+top.height; j < y+height-bottom.height; j+=center.height) {
			int h = center.height;
			if(j+center.height>y+height-bottom.height) {
				center.height -= (j+center.height)-(y+height-bottom.height);
			}
			for(int i = x+left.width; i < x+width-right.width; i+=center.width) {
				dst.x = i; dst.y = j;
				int w = center.width;
				if(i+center.width>x+width-right.width) {
					center.width -= (i+center.width)-(x+width-right.width);
				}
				//maDrawImageRegion(image, &center, &dst, TRANS_NONE);
				drawRegion(image, data, width, &center, &dst);

				center.width = w;
			}
			center.height = h;
		}
		}

		// draw borders
		if(top.width) {
		for(int i = x+left.width; i < x+width-right.width; i+=top.width) {
				dst.x = i; dst.y = y;
				dst2.x = i; dst2.y = y+height-bottom.height;

				int w1 = top.width;
				int w2 = bottom.width;
				if(i+top.width>x+width-right.width) {
					top.width -= (i+w1)-(x+width-right.width);
					bottom.width -= (i+w1)-(x+width-right.width);
				}
				//maDrawImageRegion(image, &top, &dst, TRANS_NONE);
				//maDrawImageRegion(image, &bottom, &dst2, TRANS_NONE);
				drawRegion(image, data, width, &top, &dst);
				drawRegion(image, data, width, &bottom, &dst2);

				top.width = w1;
				bottom.width = w2;
		}
		}

		if(left.height) {
		for(int i = y+top.height; i < y+height-bottom.height; i+=left.height) {
				dst.x = x; dst.y = i;
				dst2.x = x+width-right.width; dst2.y = i;

				int w1 = left.height;
				int w2 = right.height;
				if(i+left.height>y+height-bottom.height) {
					left.height -= (i+w1)-(y+height-bottom.height);
					right.height -= (i+w1)-(y+height-bottom.height);
				}
				//maDrawImageRegion(image, &left, &dst, TRANS_NONE);
				//maDrawImageRegion(image, &right, &dst2, TRANS_NONE);
				drawRegion(image, data, width, &left, &dst);
				drawRegion(image, data, width, &right, &dst2);

				left.height = w1;
				right.height = w2;
		}
		}
	}
Пример #14
0
/**************************** REGION ********************************/
void QgsGrassNewMapset::setRegionPage()
{
    QgsDebugMsg( "entered." );

    // Set defaults
    if ( !mRegionModified )
    {
        setGrassRegionDefaults();
    }

    // Create new projection
    QgsCoordinateReferenceSystem newSrs;
    if ( mProjRadioButton->isChecked() )
    {
        QgsDebugMsg( QString( "selectedCrsId() = %1" ).arg( mProjectionSelector->selectedCrsId() ) );

        if ( mProjectionSelector->selectedCrsId() > 0 )
        {
            newSrs.createFromSrsId( mProjectionSelector->selectedCrsId() );
            if ( ! newSrs.isValid() )
            {
                QMessageBox::warning( 0, tr( "Warning" ), tr( "Cannot create projection." ) );
            }
        }
    }

    // Reproject previous region if it was modified
    // and if previous and current projection is valid
    if ( mRegionModified && newSrs.isValid() && mSrs.isValid()
            && newSrs.srsid() != mSrs.srsid() )
    {
        QgsCoordinateTransform trans( mSrs, newSrs );

        double n = mNorthLineEdit->text().toDouble();
        double s = mSouthLineEdit->text().toDouble();
        double e = mEastLineEdit->text().toDouble();
        double w = mWestLineEdit->text().toDouble();

        std::vector<QgsPoint> points;

        // TODO: this is not perfect
        points.push_back( QgsPoint( w, s ) );
        points.push_back( QgsPoint( e, n ) );

        bool ok = true;
        for ( int i = 0; i < 2; i++ )
        {
            try
            {
                points[i] = trans.transform( points[i] );
            }
            catch ( QgsCsException &cse )
            {
                Q_UNUSED( cse );
                QgsDebugMsg( "Cannot transform point" );
                ok = false;
                break;
            }
        }

        if ( ok )
        {
            mNorthLineEdit->setText( QString::number( points[1].y() ) );
            mSouthLineEdit->setText( QString::number( points[0].y() ) );
            mEastLineEdit->setText( QString::number( points[1].x() ) );
            mWestLineEdit->setText( QString::number( points[0].x() ) );
        }
        else
        {
            QMessageBox::warning( 0, tr( "Warning" ), tr( "Cannot reproject previously set region, default region set." ) );

            setGrassRegionDefaults();
        }
    }

    // Set current region projection
    mSrs = newSrs;

    // Enable / disable region selection widgets
    if ( mNoProjRadioButton->isChecked() )
    {
        mRegionMap->hide();
        mCurrentRegionButton->hide();
        mRegionsComboBox->hide();
        mRegionButton->hide();
        mSetRegionFrame->hide();
    }
    else
    {
        mRegionMap->show();
        mCurrentRegionButton->show();
        mRegionsComboBox->show();
        mRegionButton->show();
        mSetRegionFrame->show();

        QgsRectangle ext = mIface->mapCanvas()->extent();

        if ( ext.xMinimum() >= ext.xMaximum() || ext.yMinimum() >= ext.yMaximum() )
        {
            mCurrentRegionButton->setEnabled( false );
        }
    }

    checkRegion();

    if ( !mNoProjRadioButton->isChecked() )
    {
        drawRegion();
    }
}
Пример #15
0
/**************************** REGION ********************************/
void QgsGrassNewMapset::setRegionPage()
{

  // Set defaults
  if ( !mRegionModified )
  {
    setGrassRegionDefaults();
  }

  // Create new projection
  QgsCoordinateReferenceSystem newCrs;
  if ( mProjRadioButton->isChecked() )
  {
    QgsDebugMsg( QString( "selectedCrsId() = %1" ).arg( mProjectionSelector->crs().srsid() ) );

    if ( mProjectionSelector->crs().srsid() > 0 )
    {
      newCrs = mProjectionSelector->crs();
      if ( ! newCrs.isValid() )
      {
        QgsGrass::warning( tr( "Cannot create projection." ) );
      }
    }
  }

  // Reproject previous region if it was modified
  // and if previous and current projection is valid
  if ( mRegionModified && newCrs.isValid() && mCrs.isValid()
       && newCrs.srsid() != mCrs.srsid() )
  {
    QgsCoordinateTransform trans( mCrs, newCrs );

    double n = mNorthLineEdit->text().toDouble();
    double s = mSouthLineEdit->text().toDouble();
    double e = mEastLineEdit->text().toDouble();
    double w = mWestLineEdit->text().toDouble();

    std::vector<QgsPointXY> points;

    // TODO: this is not perfect
    points.push_back( QgsPointXY( w, s ) );
    points.push_back( QgsPointXY( e, n ) );

    bool ok = true;
    for ( int i = 0; i < 2; i++ )
    {
      try
      {
        points[i] = trans.transform( points[i] );
      }
      catch ( QgsCsException &cse )
      {
        Q_UNUSED( cse );
        QgsDebugMsg( "Cannot transform point" );
        ok = false;
        break;
      }
    }

    if ( ok )
    {
      int precision = newCrs.mapUnits() == QgsUnitTypes::DistanceDegrees ? 6 : 1;
      mNorthLineEdit->setText( qgsDoubleToString( points[1].y(), precision ) );
      mSouthLineEdit->setText( qgsDoubleToString( points[0].y(), precision ) );
      mEastLineEdit->setText( qgsDoubleToString( points[1].x(), precision ) );
      mWestLineEdit->setText( qgsDoubleToString( points[0].x(), precision ) );
    }
    else
    {
      QgsGrass::warning( tr( "Cannot reproject previously set region, default region set." ) );
      setGrassRegionDefaults();
    }
  }

  // Set current region projection
  mCrs = newCrs;

  // Enable / disable region selection widgets
  if ( mNoProjRadioButton->isChecked() )
  {
    mRegionMap->hide();
    mCurrentRegionButton->hide();
    mRegionsComboBox->hide();
    mRegionButton->hide();
    mSetRegionFrame->hide();
  }
  else
  {
    mRegionMap->show();
    mCurrentRegionButton->show();
    mRegionsComboBox->show();
    mRegionButton->show();
    mSetRegionFrame->show();

    QgsRectangle ext = mIface->mapCanvas()->extent();

    mCurrentRegionButton->setEnabled( !ext.isEmpty() );
  }

  checkRegion();

  if ( !mNoProjRadioButton->isChecked() )
  {
    drawRegion();
  }
}
Пример #16
0
void PartitionPlane::drawRegions( QPainter *p )
{
  for( regionIt r = m_regions.begin(); r != m_regions.end(); ++r )
    drawRegion( p, *r );
}
Пример #17
0
namespace SkRecords {

// NoOps draw nothing.
template <> void Draw::draw(const NoOp&) {}

#define DRAW(T, call) template <> void Draw::draw(const T& r) { fCanvas->call; }
DRAW(Restore, restore());
DRAW(Save, save());
DRAW(SaveLayer, saveLayer(SkCanvas::SaveLayerRec(r.bounds,
                                                 r.paint,
                                                 r.backdrop.get(),
                                                 r.saveLayerFlags)));
DRAW(SetMatrix, setMatrix(SkMatrix::Concat(fInitialCTM, r.matrix)));
DRAW(Concat, concat(r.matrix));
DRAW(Translate, translate(r.dx, r.dy));

DRAW(ClipPath, clipPath(r.path, r.opAA.op, r.opAA.aa));
DRAW(ClipRRect, clipRRect(r.rrect, r.opAA.op, r.opAA.aa));
DRAW(ClipRect, clipRect(r.rect, r.opAA.op, r.opAA.aa));
DRAW(ClipRegion, clipRegion(r.region, r.op));

#ifdef SK_EXPERIMENTAL_SHADOWING
DRAW(TranslateZ, SkCanvas::translateZ(r.z));
#else
template <> void Draw::draw(const TranslateZ& r) { }
#endif

DRAW(DrawArc, drawArc(r.oval, r.startAngle, r.sweepAngle, r.useCenter, r.paint));
DRAW(DrawDRRect, drawDRRect(r.outer, r.inner, r.paint));
DRAW(DrawImage, drawImage(r.image.get(), r.left, r.top, r.paint));

template <> void Draw::draw(const DrawImageLattice& r) {
    SkCanvas::Lattice lattice;
    lattice.fXCount = r.xCount;
    lattice.fXDivs = r.xDivs;
    lattice.fYCount = r.yCount;
    lattice.fYDivs = r.yDivs;
    lattice.fFlags = (0 == r.flagCount) ? nullptr : r.flags;
    lattice.fBounds = &r.src;
    fCanvas->drawImageLattice(r.image.get(), lattice, r.dst, r.paint);
}

DRAW(DrawImageRect, legacy_drawImageRect(r.image.get(), r.src, r.dst, r.paint, r.constraint));
DRAW(DrawImageNine, drawImageNine(r.image.get(), r.center, r.dst, r.paint));
DRAW(DrawOval, drawOval(r.oval, r.paint));
DRAW(DrawPaint, drawPaint(r.paint));
DRAW(DrawPath, drawPath(r.path, r.paint));
DRAW(DrawPatch, drawPatch(r.cubics, r.colors, r.texCoords, r.xmode, r.paint));
DRAW(DrawPicture, drawPicture(r.picture.get(), &r.matrix, r.paint));

#ifdef SK_EXPERIMENTAL_SHADOWING
DRAW(DrawShadowedPicture, drawShadowedPicture(r.picture.get(), &r.matrix, r.paint, r.params));
#else
template <> void Draw::draw(const DrawShadowedPicture& r) { }
#endif

DRAW(DrawPoints, drawPoints(r.mode, r.count, r.pts, r.paint));
DRAW(DrawPosText, drawPosText(r.text, r.byteLength, r.pos, r.paint));
DRAW(DrawPosTextH, drawPosTextH(r.text, r.byteLength, r.xpos, r.y, r.paint));
DRAW(DrawRRect, drawRRect(r.rrect, r.paint));
DRAW(DrawRect, drawRect(r.rect, r.paint));
DRAW(DrawRegion, drawRegion(r.region, r.paint));
DRAW(DrawText, drawText(r.text, r.byteLength, r.x, r.y, r.paint));
DRAW(DrawTextBlob, drawTextBlob(r.blob.get(), r.x, r.y, r.paint));
DRAW(DrawTextOnPath, drawTextOnPath(r.text, r.byteLength, r.path, &r.matrix, r.paint));
DRAW(DrawTextRSXform, drawTextRSXform(r.text, r.byteLength, r.xforms, r.cull, r.paint));
DRAW(DrawAtlas, drawAtlas(r.atlas.get(),
                          r.xforms, r.texs, r.colors, r.count, r.mode, r.cull, r.paint));
DRAW(DrawVertices, drawVertices(r.vmode, r.vertexCount, r.vertices, r.texs, r.colors,
                                r.xmode, r.indices, r.indexCount, r.paint));
DRAW(DrawAnnotation, drawAnnotation(r.rect, r.key.c_str(), r.value.get()));
#undef DRAW

template <> void Draw::draw(const DrawDrawable& r) {
    SkASSERT(r.index >= 0);
    SkASSERT(r.index < fDrawableCount);
    if (fDrawables) {
        SkASSERT(nullptr == fDrawablePicts);
        fCanvas->drawDrawable(fDrawables[r.index], r.matrix);
    } else {
        fCanvas->drawPicture(fDrawablePicts[r.index], r.matrix, nullptr);
    }
}

// This is an SkRecord visitor that fills an SkBBoxHierarchy.
//
// The interesting part here is how to calculate bounds for ops which don't
// have intrinsic bounds.  What is the bounds of a Save or a Translate?
//
// We answer this by thinking about a particular definition of bounds: if I
// don't execute this op, pixels in this rectangle might draw incorrectly.  So
// the bounds of a Save, a Translate, a Restore, etc. are the union of the
// bounds of Draw* ops that they might have an effect on.  For any given
// Save/Restore block, the bounds of the Save, the Restore, and any other
// non-drawing ("control") ops inside are exactly the union of the bounds of
// the drawing ops inside that block.
//
// To implement this, we keep a stack of active Save blocks.  As we consume ops
// inside the Save/Restore block, drawing ops are unioned with the bounds of
// the block, and control ops are stashed away for later.  When we finish the
// block with a Restore, our bounds are complete, and we go back and fill them
// in for all the control ops we stashed away.
class FillBounds : SkNoncopyable {
public:
    FillBounds(const SkRect& cullRect, const SkRecord& record, SkRect bounds[])
        : fNumRecords(record.count())
        , fCullRect(cullRect)
        , fBounds(bounds) {
        fCTM = SkMatrix::I();
        fCurrentClipBounds = fCullRect;
    }

    void cleanUp() {
        // If we have any lingering unpaired Saves, simulate restores to make
        // sure all ops in those Save blocks have their bounds calculated.
        while (!fSaveStack.isEmpty()) {
            this->popSaveBlock();
        }

        // Any control ops not part of any Save/Restore block draw everywhere.
        while (!fControlIndices.isEmpty()) {
            this->popControl(fCullRect);
        }
    }

    void setCurrentOp(int currentOp) { fCurrentOp = currentOp; }


    template <typename T> void operator()(const T& op) {
        this->updateCTM(op);
        this->updateClipBounds(op);
        this->trackBounds(op);
    }

    // In this file, SkRect are in local coordinates, Bounds are translated back to identity space.
    typedef SkRect Bounds;

    int currentOp() const { return fCurrentOp; }
    const SkMatrix& ctm() const { return fCTM; }
    const Bounds& getBounds(int index) const { return fBounds[index]; }

    // Adjust rect for all paints that may affect its geometry, then map it to identity space.
    Bounds adjustAndMap(SkRect rect, const SkPaint* paint) const {
        // Inverted rectangles really confuse our BBHs.
        rect.sort();

        // Adjust the rect for its own paint.
        if (!AdjustForPaint(paint, &rect)) {
            // The paint could do anything to our bounds.  The only safe answer is the current clip.
            return fCurrentClipBounds;
        }

        // Adjust rect for all the paints from the SaveLayers we're inside.
        if (!this->adjustForSaveLayerPaints(&rect)) {
            // Same deal as above.
            return fCurrentClipBounds;
        }

        // Map the rect back to identity space.
        fCTM.mapRect(&rect);

        // Nothing can draw outside the current clip.
        if (!rect.intersect(fCurrentClipBounds)) {
            return Bounds::MakeEmpty();
        }

        return rect;
    }

private:
    struct SaveBounds {
        int controlOps;        // Number of control ops in this Save block, including the Save.
        Bounds bounds;         // Bounds of everything in the block.
        const SkPaint* paint;  // Unowned.  If set, adjusts the bounds of all ops in this block.
        SkMatrix ctm;
    };

    // Only Restore, SetMatrix, Concat, and Translate change the CTM.
    template <typename T> void updateCTM(const T&) {}
    void updateCTM(const Restore& op)   { fCTM = op.matrix; }
    void updateCTM(const SetMatrix& op) { fCTM = op.matrix; }
    void updateCTM(const Concat& op)    { fCTM.preConcat(op.matrix); }
    void updateCTM(const Translate& op) { fCTM.preTranslate(op.dx, op.dy); }

    // Most ops don't change the clip.
    template <typename T> void updateClipBounds(const T&) {}

    // Clip{Path,RRect,Rect,Region} obviously change the clip.  They all know their bounds already.
    void updateClipBounds(const ClipPath&   op) { this->updateClipBoundsForClipOp(op.devBounds); }
    void updateClipBounds(const ClipRRect&  op) { this->updateClipBoundsForClipOp(op.devBounds); }
    void updateClipBounds(const ClipRect&   op) { this->updateClipBoundsForClipOp(op.devBounds); }
    void updateClipBounds(const ClipRegion& op) { this->updateClipBoundsForClipOp(op.devBounds); }

    // The bounds of clip ops need to be adjusted for the paints of saveLayers they're inside.
    void updateClipBoundsForClipOp(const SkIRect& devBounds) {
        Bounds clip = SkRect::Make(devBounds);
        // We don't call adjustAndMap() because as its last step it would intersect the adjusted
        // clip bounds with the previous clip, exactly what we can't do when the clip grows.
        if (this->adjustForSaveLayerPaints(&clip)) {
            fCurrentClipBounds = clip.intersect(fCullRect) ? clip : Bounds::MakeEmpty();
        } else {
            fCurrentClipBounds = fCullRect;
        }
    }

    // Restore holds the devBounds for the clip after the {save,saveLayer}/restore block completes.
    void updateClipBounds(const Restore& op) {
        // This is just like the clip ops above, but we need to skip the effects (if any) of our
        // paired saveLayer (if it is one); it has not yet been popped off the save stack.  Our
        // devBounds reflect the state of the world after the saveLayer/restore block is done,
        // so they are not affected by the saveLayer's paint.
        const int kSavesToIgnore = 1;
        Bounds clip = SkRect::Make(op.devBounds);
        if (this->adjustForSaveLayerPaints(&clip, kSavesToIgnore)) {
            fCurrentClipBounds = clip.intersect(fCullRect) ? clip : Bounds::MakeEmpty();
        } else {
            fCurrentClipBounds = fCullRect;
        }
    }

    // We also take advantage of SaveLayer bounds when present to further cut the clip down.
    void updateClipBounds(const SaveLayer& op)  {
        if (op.bounds) {
            // adjustAndMap() intersects these layer bounds with the previous clip for us.
            fCurrentClipBounds = this->adjustAndMap(*op.bounds, op.paint);
        }
    }

    // The bounds of these ops must be calculated when we hit the Restore
    // from the bounds of the ops in the same Save block.
    void trackBounds(const Save&)          { this->pushSaveBlock(nullptr); }
    void trackBounds(const SaveLayer& op)  { this->pushSaveBlock(op.paint); }
    void trackBounds(const Restore&) { fBounds[fCurrentOp] = this->popSaveBlock(); }

    void trackBounds(const SetMatrix&)         { this->pushControl(); }
    void trackBounds(const Concat&)            { this->pushControl(); }
    void trackBounds(const Translate&)         { this->pushControl(); }
    void trackBounds(const TranslateZ&)        { this->pushControl(); }
    void trackBounds(const ClipRect&)          { this->pushControl(); }
    void trackBounds(const ClipRRect&)         { this->pushControl(); }
    void trackBounds(const ClipPath&)          { this->pushControl(); }
    void trackBounds(const ClipRegion&)        { this->pushControl(); }


    // For all other ops, we can calculate and store the bounds directly now.
    template <typename T> void trackBounds(const T& op) {
        fBounds[fCurrentOp] = this->bounds(op);
        this->updateSaveBounds(fBounds[fCurrentOp]);
    }

    void pushSaveBlock(const SkPaint* paint) {
        // Starting a new Save block.  Push a new entry to represent that.
        SaveBounds sb;
        sb.controlOps = 0;
        // If the paint affects transparent black, the bound shouldn't be smaller
        // than the current clip bounds.
        sb.bounds =
            PaintMayAffectTransparentBlack(paint) ? fCurrentClipBounds : Bounds::MakeEmpty();
        sb.paint = paint;
        sb.ctm = this->fCTM;

        fSaveStack.push(sb);
        this->pushControl();
    }

    static bool PaintMayAffectTransparentBlack(const SkPaint* paint) {
        if (paint) {
            // FIXME: this is very conservative
            if (paint->getImageFilter() || paint->getColorFilter()) {
                return true;
            }

            // Unusual blendmodes require us to process a saved layer
            // even with operations outisde the clip.
            // For example, DstIn is used by masking layers.
            // https://code.google.com/p/skia/issues/detail?id=1291
            // https://crbug.com/401593
            switch (paint->getBlendMode()) {
                // For each of the following transfer modes, if the source
                // alpha is zero (our transparent black), the resulting
                // blended alpha is not necessarily equal to the original
                // destination alpha.
                case SkBlendMode::kClear:
                case SkBlendMode::kSrc:
                case SkBlendMode::kSrcIn:
                case SkBlendMode::kDstIn:
                case SkBlendMode::kSrcOut:
                case SkBlendMode::kDstATop:
                case SkBlendMode::kModulate:
                    return true;
                    break;
                default:
                    break;
            }
        }
        return false;
    }

    Bounds popSaveBlock() {
        // We're done the Save block.  Apply the block's bounds to all control ops inside it.
        SaveBounds sb;
        fSaveStack.pop(&sb);

        while (sb.controlOps --> 0) {
            this->popControl(sb.bounds);
        }

        // This whole Save block may be part another Save block.
        this->updateSaveBounds(sb.bounds);

        // If called from a real Restore (not a phony one for balance), it'll need the bounds.
        return sb.bounds;
    }

    void pushControl() {
        fControlIndices.push(fCurrentOp);
        if (!fSaveStack.isEmpty()) {
            fSaveStack.top().controlOps++;
        }
    }

    void popControl(const Bounds& bounds) {
        fBounds[fControlIndices.top()] = bounds;
        fControlIndices.pop();
    }

    void updateSaveBounds(const Bounds& bounds) {
        // If we're in a Save block, expand its bounds to cover these bounds too.
        if (!fSaveStack.isEmpty()) {
            fSaveStack.top().bounds.join(bounds);
        }
    }

    // FIXME: this method could use better bounds
    Bounds bounds(const DrawText&) const { return fCurrentClipBounds; }

    Bounds bounds(const DrawPaint&) const { return fCurrentClipBounds; }
    Bounds bounds(const NoOp&)  const { return Bounds::MakeEmpty(); }    // NoOps don't draw.

    Bounds bounds(const DrawRect& op) const { return this->adjustAndMap(op.rect, &op.paint); }
    Bounds bounds(const DrawRegion& op) const {
        SkRect rect = SkRect::Make(op.region.getBounds());
        return this->adjustAndMap(rect, &op.paint);
    }
    Bounds bounds(const DrawOval& op) const { return this->adjustAndMap(op.oval, &op.paint); }
    // Tighter arc bounds?
    Bounds bounds(const DrawArc& op) const { return this->adjustAndMap(op.oval, &op.paint); }
    Bounds bounds(const DrawRRect& op) const {
        return this->adjustAndMap(op.rrect.rect(), &op.paint);
    }
    Bounds bounds(const DrawDRRect& op) const {
        return this->adjustAndMap(op.outer.rect(), &op.paint);
    }
    Bounds bounds(const DrawImage& op) const {
        const SkImage* image = op.image.get();
        SkRect rect = SkRect::MakeXYWH(op.left, op.top, image->width(), image->height());

        return this->adjustAndMap(rect, op.paint);
    }
    Bounds bounds(const DrawImageLattice& op) const {
        return this->adjustAndMap(op.dst, op.paint);
    }
    Bounds bounds(const DrawImageRect& op) const {
        return this->adjustAndMap(op.dst, op.paint);
    }
    Bounds bounds(const DrawImageNine& op) const {
        return this->adjustAndMap(op.dst, op.paint);
    }
    Bounds bounds(const DrawPath& op) const {
        return op.path.isInverseFillType() ? fCurrentClipBounds
                                           : this->adjustAndMap(op.path.getBounds(), &op.paint);
    }
    Bounds bounds(const DrawPoints& op) const {
        SkRect dst;
        dst.set(op.pts, op.count);

        // Pad the bounding box a little to make sure hairline points' bounds aren't empty.
        SkScalar stroke = SkMaxScalar(op.paint.getStrokeWidth(), 0.01f);
        dst.outset(stroke/2, stroke/2);

        return this->adjustAndMap(dst, &op.paint);
    }
    Bounds bounds(const DrawPatch& op) const {
        SkRect dst;
        dst.set(op.cubics, SkPatchUtils::kNumCtrlPts);
        return this->adjustAndMap(dst, &op.paint);
    }
    Bounds bounds(const DrawVertices& op) const {
        SkRect dst;
        dst.set(op.vertices, op.vertexCount);
        return this->adjustAndMap(dst, &op.paint);
    }

    Bounds bounds(const DrawAtlas& op) const {
        if (op.cull) {
            // TODO: <reed> can we pass nullptr for the paint? Isn't cull already "correct"
            // for the paint (by the caller)?
            return this->adjustAndMap(*op.cull, op.paint);
        } else {
            return fCurrentClipBounds;
        }
    }

    Bounds bounds(const DrawPicture& op) const {
        SkRect dst = op.picture->cullRect();
        op.matrix.mapRect(&dst);
        return this->adjustAndMap(dst, op.paint);
    }

    Bounds bounds(const DrawShadowedPicture& op) const {
        SkRect dst = op.picture->cullRect();
        op.matrix.mapRect(&dst);
        return this->adjustAndMap(dst, op.paint);
    }

    Bounds bounds(const DrawPosText& op) const {
        const int N = op.paint.countText(op.text, op.byteLength);
        if (N == 0) {
            return Bounds::MakeEmpty();
        }

        SkRect dst;
        dst.set(op.pos, N);
        AdjustTextForFontMetrics(&dst, op.paint);
        return this->adjustAndMap(dst, &op.paint);
    }
    Bounds bounds(const DrawPosTextH& op) const {
        const int N = op.paint.countText(op.text, op.byteLength);
        if (N == 0) {
            return Bounds::MakeEmpty();
        }

        SkScalar left = op.xpos[0], right = op.xpos[0];
        for (int i = 1; i < N; i++) {
            left  = SkMinScalar(left,  op.xpos[i]);
            right = SkMaxScalar(right, op.xpos[i]);
        }
        SkRect dst = { left, op.y, right, op.y };
        AdjustTextForFontMetrics(&dst, op.paint);
        return this->adjustAndMap(dst, &op.paint);
    }
    Bounds bounds(const DrawTextOnPath& op) const {
        SkRect dst = op.path.getBounds();

        // Pad all sides by the maximum padding in any direction we'd normally apply.
        SkRect pad = { 0, 0, 0, 0};
        AdjustTextForFontMetrics(&pad, op.paint);

        // That maximum padding happens to always be the right pad today.
        SkASSERT(pad.fLeft == -pad.fRight);
        SkASSERT(pad.fTop  == -pad.fBottom);
        SkASSERT(pad.fRight > pad.fBottom);
        dst.outset(pad.fRight, pad.fRight);

        return this->adjustAndMap(dst, &op.paint);
    }

    Bounds bounds(const DrawTextRSXform& op) const {
        if (op.cull) {
            return this->adjustAndMap(*op.cull, nullptr);
        } else {
            return fCurrentClipBounds;
        }
    }

    Bounds bounds(const DrawTextBlob& op) const {
        SkRect dst = op.blob->bounds();
        dst.offset(op.x, op.y);
        return this->adjustAndMap(dst, &op.paint);
    }

    Bounds bounds(const DrawDrawable& op) const {
        return this->adjustAndMap(op.worstCaseBounds, nullptr);
    }

    Bounds bounds(const DrawAnnotation& op) const {
        return this->adjustAndMap(op.rect, nullptr);
    }

    static void AdjustTextForFontMetrics(SkRect* rect, const SkPaint& paint) {
#ifdef SK_DEBUG
        SkRect correct = *rect;
#endif
        // crbug.com/373785 ~~> xPad = 4x yPad
        // crbug.com/424824 ~~> bump yPad from 2x text size to 2.5x
        const SkScalar yPad = 2.5f * paint.getTextSize(),
                       xPad = 4.0f * yPad;
        rect->outset(xPad, yPad);
#ifdef SK_DEBUG
        SkPaint::FontMetrics metrics;
        paint.getFontMetrics(&metrics);
        correct.fLeft   += metrics.fXMin;
        correct.fTop    += metrics.fTop;
        correct.fRight  += metrics.fXMax;
        correct.fBottom += metrics.fBottom;
        // See skia:2862 for why we ignore small text sizes.
        SkASSERTF(paint.getTextSize() < 0.001f || rect->contains(correct),
                  "%f %f %f %f vs. %f %f %f %f\n",
                  -xPad, -yPad, +xPad, +yPad,
                  metrics.fXMin, metrics.fTop, metrics.fXMax, metrics.fBottom);
#endif
    }

    // Returns true if rect was meaningfully adjusted for the effects of paint,
    // false if the paint could affect the rect in unknown ways.
    static bool AdjustForPaint(const SkPaint* paint, SkRect* rect) {
        if (paint) {
            if (paint->canComputeFastBounds()) {
                *rect = paint->computeFastBounds(*rect, rect);
                return true;
            }
            return false;
        }
        return true;
    }

    bool adjustForSaveLayerPaints(SkRect* rect, int savesToIgnore = 0) const {
        for (int i = fSaveStack.count() - 1 - savesToIgnore; i >= 0; i--) {
            SkMatrix inverse;
            if (!fSaveStack[i].ctm.invert(&inverse)) {
                return false;
            }
            inverse.mapRect(rect);
            if (!AdjustForPaint(fSaveStack[i].paint, rect)) {
                return false;
            }
            fSaveStack[i].ctm.mapRect(rect);
        }
        return true;
    }

    const int fNumRecords;

    // We do not guarantee anything for operations outside of the cull rect
    const SkRect fCullRect;

    // Conservative identity-space bounds for each op in the SkRecord.
    Bounds* fBounds;

    // We walk fCurrentOp through the SkRecord, as we go using updateCTM()
    // and updateClipBounds() to maintain the exact CTM (fCTM) and conservative
    // identity-space bounds of the current clip (fCurrentClipBounds).
    int fCurrentOp;
    SkMatrix fCTM;
    Bounds fCurrentClipBounds;

    // Used to track the bounds of Save/Restore blocks and the control ops inside them.
    SkTDArray<SaveBounds> fSaveStack;
    SkTDArray<int>   fControlIndices;
};

}  // namespace SkRecords