Пример #1
0
void InlineFlowBoxPainter::paintBoxDecorationBackground(const PaintInfo& paintInfo, const LayoutPoint& paintOffset, const LayoutRect& cullRect)
{
    ASSERT(paintInfo.phase == PaintPhaseForeground);
    if (m_inlineFlowBox.lineLayoutItem().style()->visibility() != VISIBLE)
        return;

    // You can use p::first-line to specify a background. If so, the root line boxes for
    // a line may actually have to paint a background.
    LayoutObject* inlineFlowBoxLayoutObject = LineLayoutAPIShim::layoutObjectFrom(m_inlineFlowBox.lineLayoutItem());
    const ComputedStyle* styleToUse = m_inlineFlowBox.lineLayoutItem().style(m_inlineFlowBox.isFirstLineStyle());
    bool shouldPaintBoxDecorationBackground;
    if (m_inlineFlowBox.parent())
        shouldPaintBoxDecorationBackground = inlineFlowBoxLayoutObject->hasBoxDecorationBackground();
    else
        shouldPaintBoxDecorationBackground = m_inlineFlowBox.isFirstLineStyle() && styleToUse != m_inlineFlowBox.lineLayoutItem().style();

    if (!shouldPaintBoxDecorationBackground)
        return;

    if (DrawingRecorder::useCachedDrawingIfPossible(paintInfo.context, m_inlineFlowBox, DisplayItem::BoxDecorationBackground))
        return;

    DrawingRecorder recorder(paintInfo.context, m_inlineFlowBox, DisplayItem::BoxDecorationBackground, pixelSnappedIntRect(cullRect));

    LayoutRect frameRect = frameRectClampedToLineTopAndBottomIfNeeded();

    // Move x/y to our coordinates.
    LayoutRect localRect(frameRect);
    m_inlineFlowBox.flipForWritingMode(localRect);
    LayoutPoint adjustedPaintOffset = paintOffset + localRect.location();

    LayoutRect adjustedFrameRect = LayoutRect(adjustedPaintOffset, frameRect.size());

    IntRect adjustedClipRect;
    BorderPaintingType borderPaintingType = getBorderPaintType(adjustedFrameRect, adjustedClipRect);

    // Shadow comes first and is behind the background and border.
    if (!m_inlineFlowBox.boxModelObject().boxShadowShouldBeAppliedToBackground(BackgroundBleedNone, &m_inlineFlowBox))
        paintBoxShadow(paintInfo, *styleToUse, Normal, adjustedFrameRect);

    Color backgroundColor = inlineFlowBoxLayoutObject->resolveColor(*styleToUse, CSSPropertyBackgroundColor);
    paintFillLayers(paintInfo, backgroundColor, styleToUse->backgroundLayers(), adjustedFrameRect);
    paintBoxShadow(paintInfo, *styleToUse, Inset, adjustedFrameRect);

    switch (borderPaintingType) {
    case DontPaintBorders:
        break;
    case PaintBordersWithoutClip:
        BoxPainter::paintBorder(*toLayoutBoxModelObject(LineLayoutAPIShim::layoutObjectFrom(m_inlineFlowBox.boxModelObject())), paintInfo, adjustedFrameRect, m_inlineFlowBox.lineLayoutItem().styleRef(m_inlineFlowBox.isFirstLineStyle()), BackgroundBleedNone, m_inlineFlowBox.includeLogicalLeftEdge(), m_inlineFlowBox.includeLogicalRightEdge());
        break;
    case PaintBordersWithClip:
        // FIXME: What the heck do we do with RTL here? The math we're using is obviously not right,
        // but it isn't even clear how this should work at all.
        LayoutRect imageStripPaintRect = paintRectForImageStrip(adjustedPaintOffset, frameRect.size(), LTR);
        GraphicsContextStateSaver stateSaver(paintInfo.context);
        paintInfo.context.clip(adjustedClipRect);
        BoxPainter::paintBorder(*toLayoutBoxModelObject(LineLayoutAPIShim::layoutObjectFrom(m_inlineFlowBox.boxModelObject())), paintInfo, imageStripPaintRect, m_inlineFlowBox.lineLayoutItem().styleRef(m_inlineFlowBox.isFirstLineStyle()));
        break;
    }
}
Пример #2
0
void TwButton::paintBackground(TwPainter* painter)
{
    TwColor fillColor;
    TwColor boundColor;
    if (isEnabled())
    {
        switch (frameState())
        {
        case Tw::State_Normal:
            {
                fillColor = backgroundColor();
                boundColor = k_DefBoundColor;
            }
            break;
        case Tw::State_Active:
            {
                fillColor = m_activeColor;
                boundColor = m_activeColor;
                boundColor.setAlpha(255);
            }
            break;
        case Tw::State_Pressed:
            {
                fillColor = m_pressedColor;
                boundColor = m_pressedColor;
                boundColor.setAlpha(255);
            }
            break;
        }
    }
    else
    {
        fillColor = k_DefDisableFillColor;
        boundColor = k_DefBoundColor;
    }
    if (fillColor.alpha() != 0)
    {
        painter->fillRect(localRect(), fillColor);
    }
    if (boundColor.alpha() != 0)
    {
        painter->drawRect(localRect(), boundColor, 1.0f, true);
    }
}
Пример #3
0
	bool Button::onMouseDrag(int2 start, int2 current, int key, int is_final) {
		m_mouse_press = key == 0 && !is_final && m_is_enabled;
		if(key == 0 && m_is_enabled) {
			if(is_final == 1 && localRect().isInside(current))
				sendEvent(this, Event::button_clicked, m_id);
			return true;
		}

		return false;
	}
Пример #4
0
IntRect CaretBase::absoluteBoundsForLocalRect(Node* node, const LayoutRect& rect) const
{
    LayoutBlock* caretPainter = caretLayoutObject(node);
    if (!caretPainter)
        return IntRect();

    LayoutRect localRect(rect);
    caretPainter->flipForWritingMode(localRect);
    return caretPainter->localToAbsoluteQuad(FloatRect(localRect)).enclosingBoundingBox();
}
Пример #5
0
void Timer::draw()
	{
	glBegin( GL_QUADS );
		// top
		Color(0.333).scaleValue( 0.75 ).withAlpha( 0.75 ).set();
		glVertex2i( 0, 0 );
		glVertex2i( bounds.w, 0 );

		// bottom
		Color(0.333).scaleValue( 0.25 ).withAlpha( 0.5 ).set();
		glVertex2i( bounds.w, bounds.h );
		glVertex2i( 0, bounds.h );
	glEnd();

	if ( enabled())
		{
		// draw pulsing Pause symbol
		Color(0.333).scaleValue( m_ticks & 1 ? 0.8 : 1.0 ).withAlpha(0.5).set();
		glBegin( GL_QUADS );
			glVertex2i( 7, 7 );
			glVertex2i( 13, 7 );
			glVertex2i( 13, 23 );
			glVertex2i( 7, 23 );
			// prime numbers are good!
			glVertex2i( 17, 7 );
			glVertex2i( 23, 7 );
			glVertex2i( 23, 23 );
			glVertex2i( 17, 23 );
		glEnd();
		}
	else
		{
		// draw Run symbol
		Color(0.333).withAlpha(0.5).set();
		glBegin( GL_TRIANGLES );
			glVertex2i( 8, 5 );
			glVertex2i( 23, 15 );
			glVertex2i( 8, 24 );
		glEnd();

		Color(0.333).set();
		glEnable( GL_LINE_SMOOTH );
		glBegin( GL_LINE_LOOP );
			glVertex2i( 8, 5 );
			glVertex2i( 23, 15 );
			glVertex2i( 8, 24 );
		glEnd();
		glDisable( GL_LINE_SMOOTH );
		}

	glColor4f( 0, 0, 0, 0.5 );
	localRect().stroke();
	}
void RenderInline::absoluteQuads(Vector<FloatQuad>& quads, bool topLevel)
{
    for (InlineRunBox* curr = firstLineBox(); curr; curr = curr->nextLineBox()) {
        FloatRect localRect(curr->xPos(), curr->yPos(), curr->width(), curr->height());
        quads.append(localToAbsoluteQuad(localRect));
    }
    
    for (RenderObject* curr = firstChild(); curr; curr = curr->nextSibling()) {
        if (!curr->isText())
            curr->absoluteQuads(quads, false);
    }

    if (continuation() && topLevel)
        continuation()->absoluteQuads(quads, topLevel);
}
Пример #7
0
	bool ImageButton::onMouseDrag(int2 start, int2 current, int key, int is_final) {
		if(key == 0 && !m_mouse_press && !m_proto.sound_name.empty() && !(m_mode == mode_toggle_on && m_is_pressed))
			audio::playSound(m_proto.sound_name.c_str(), 1.0f);
		m_mouse_press = key == 0 && !is_final && m_is_enabled;

		if(key == 0 && m_is_enabled) {
			if(is_final == 1 && localRect().isInside(current)) {
				if(m_mode == mode_toggle)
					m_is_pressed ^= 1;
				else if(m_mode == mode_toggle_on)
					m_is_pressed = true;
				sendEvent(this, Event::button_clicked, m_id);
			}
			return true;
		}

		return false;
	}
Пример #8
0
void PaintLayerClipper::mapLocalToRootWithGeometryMapper(
    const ClipRectsContext& context,
    LayoutRect& layoutRect) const {
  DCHECK(m_geometryMapper);
  bool success;

  const ObjectPaintProperties::PropertyTreeStateWithOffset*
      layerBorderBoxProperties =
          m_layer.layoutObject()->paintProperties()->localBorderBoxProperties();
  FloatRect localRect(layoutRect);
  localRect.moveBy(FloatPoint(layerBorderBoxProperties->paintOffset));

  layoutRect = LayoutRect(m_geometryMapper->mapRectToDestinationSpace(
      localRect, layerBorderBoxProperties->propertyTreeState,
      context.rootLayer->layoutObject()
          ->paintProperties()
          ->localBorderBoxProperties()
          ->propertyTreeState,
      success));
  DCHECK(success);
}
QRectF ShaderNodeUI::boundingRect() const
{
	QRectF localRect(QPointF(), QSizeF(m_nodeSize.width() + (kNodePortRadius * 2.0f), m_nodeSize.height()));
	return localRect;
}
Пример #10
0
bool View::containsPoint(SkPoint point, View * reference)
{
    SkPoint p = convertToLocal(point, reference);
    return localRect().intersects(p.x(), p.y(), p.x() + 1, p.y() + 1);
}
Пример #11
0
void InlineFlowBoxPainter::paintMask(const PaintInfo& paintInfo, const LayoutPoint& paintOffset)
{
    if (m_inlineFlowBox.lineLayoutItem().style()->visibility() != VISIBLE || paintInfo.phase != PaintPhaseMask)
        return;

    LayoutRect frameRect = frameRectClampedToLineTopAndBottomIfNeeded();

    // Move x/y to our coordinates.
    LayoutRect localRect(frameRect);
    m_inlineFlowBox.flipForWritingMode(localRect);
    LayoutPoint adjustedPaintOffset = paintOffset + localRect.location();

    const NinePieceImage& maskNinePieceImage = m_inlineFlowBox.lineLayoutItem().style()->maskBoxImage();
    StyleImage* maskBoxImage = m_inlineFlowBox.lineLayoutItem().style()->maskBoxImage().image();

    // Figure out if we need to push a transparency layer to render our mask.
    bool pushTransparencyLayer = false;
    bool compositedMask = m_inlineFlowBox.lineLayoutItem().hasLayer() && m_inlineFlowBox.boxModelObject().layer()->hasCompositedMask();
    bool flattenCompositingLayers = paintInfo.globalPaintFlags() & GlobalPaintFlattenCompositingLayers;
    SkXfermode::Mode compositeOp = SkXfermode::kSrcOver_Mode;
    if (!compositedMask || flattenCompositingLayers) {
        if ((maskBoxImage && m_inlineFlowBox.lineLayoutItem().style()->maskLayers().hasImage()) || m_inlineFlowBox.lineLayoutItem().style()->maskLayers().next()) {
            pushTransparencyLayer = true;
            paintInfo.context.beginLayer(1.0f, SkXfermode::kDstIn_Mode);
        } else {
            // TODO(fmalita): passing a dst-in xfer mode down to paintFillLayers/paintNinePieceImage
            //   seems dangerous: it is only correct if applied atomically (single draw call). While
            //   the heuristic above presumably ensures that is the case, this approach seems super
            //   fragile. We should investigate dropping this optimization in favour of the more
            //   robust layer branch above.
            compositeOp = SkXfermode::kDstIn_Mode;
        }
    }

    LayoutRect paintRect = LayoutRect(adjustedPaintOffset, frameRect.size());
    paintFillLayers(paintInfo, Color::transparent, m_inlineFlowBox.lineLayoutItem().style()->maskLayers(), paintRect, compositeOp);

    bool hasBoxImage = maskBoxImage && maskBoxImage->canRender();
    if (!hasBoxImage || !maskBoxImage->isLoaded()) {
        if (pushTransparencyLayer)
            paintInfo.context.endLayer();
        return; // Don't paint anything while we wait for the image to load.
    }

    LayoutBoxModelObject* boxModel = toLayoutBoxModelObject(LineLayoutAPIShim::layoutObjectFrom(m_inlineFlowBox.boxModelObject()));
    // The simple case is where we are the only box for this object. In those
    // cases only a single call to draw is required.
    if (!m_inlineFlowBox.prevLineBox() && !m_inlineFlowBox.nextLineBox()) {
        BoxPainter::paintNinePieceImage(*boxModel, paintInfo.context, paintRect, m_inlineFlowBox.lineLayoutItem().styleRef(), maskNinePieceImage, compositeOp);
    } else {
        // We have a mask image that spans multiple lines.
        // FIXME: What the heck do we do with RTL here? The math we're using is obviously not right,
        // but it isn't even clear how this should work at all.
        LayoutRect imageStripPaintRect = paintRectForImageStrip(adjustedPaintOffset, frameRect.size(), LTR);
        FloatRect clipRect(clipRectForNinePieceImageStrip(m_inlineFlowBox, maskNinePieceImage, paintRect));
        GraphicsContextStateSaver stateSaver(paintInfo.context);
        // TODO(chrishtr): this should be pixel-snapped.
        paintInfo.context.clip(clipRect);
        BoxPainter::paintNinePieceImage(*boxModel, paintInfo.context, imageStripPaintRect, m_inlineFlowBox.lineLayoutItem().styleRef(), maskNinePieceImage, compositeOp);
    }

    if (pushTransparencyLayer)
        paintInfo.context.endLayer();
}
Пример #12
0
bool Star::AutoFind(const usImage& image, int extraEdgeAllowance, int searchRegion)
{
    if (!image.Subframe.IsEmpty())
    {
        Debug.AddLine("Autofind called on subframe, returning error");
        return false; // not found
    }

    wxBusyCursor busy;

    Debug.AddLine(wxString::Format("Star::AutoFind called with edgeAllowance = %d searchRegion = %d", extraEdgeAllowance, searchRegion));

    // run a 3x3 median first to eliminate hot pixels
    usImage smoothed;
    smoothed.CopyFrom(image);
    Median3(smoothed);

    // convert to floating point
    FloatImg conv(smoothed);

    // downsample the source image
    const int downsample = 1;
    if (downsample > 1)
    {
        FloatImg tmp;
        Downsample(tmp, conv, downsample);
        conv.Swap(tmp);
    }

    // run the PSF convolution
    {
        FloatImg tmp;
        psf_conv(tmp, conv);
        conv.Swap(tmp);
    }

    enum { CONV_RADIUS = 4 };
    int dw = conv.Size.GetWidth();      // width of the downsampled image
    int dh = conv.Size.GetHeight();     // height of the downsampled image
    wxRect convRect(CONV_RADIUS, CONV_RADIUS, dw - 2 * CONV_RADIUS, dh - 2 * CONV_RADIUS);  // region containing valid data

    SaveImage(conv, "PHD2_AutoFind.fit");

    enum { TOP_N = 100 };  // keep track of the brightest stars
    std::set<Peak> stars;  // sorted by ascending intensity

    double global_mean, global_stdev;
    GetStats(&global_mean, &global_stdev, conv, convRect);

    Debug.AddLine("AutoFind: global mean = %.1f, stdev %.1f", global_mean, global_stdev);

    const double threshold = 0.1;
    Debug.AddLine("AutoFind: using threshold = %.1f", threshold);

    // find each local maximum
    int srch = 4;
    for (int y = convRect.GetTop() + srch; y <= convRect.GetBottom() - srch; y++)
    {
        for (int x = convRect.GetLeft() + srch; x <= convRect.GetRight() - srch; x++)
        {
            float val = conv.px[dw * y + x];
            bool ismax = false;
            if (val > 0.0)
            {
                ismax = true;
                for (int j = -srch; j <= srch; j++)
                {
                    for (int i = -srch; i <= srch; i++)
                    {
                        if (i == 0 && j == 0)
                            continue;
                        if (conv.px[dw * (y + j) + (x + i)] > val)
                        {
                            ismax = false;
                            break;
                        }
                    }
                }
            }
            if (!ismax)
                continue;

            // compare local maximum to mean value of surrounding pixels
            const int local = 7;
            double local_mean, local_stdev;
            wxRect localRect(x - local, y - local, 2 * local + 1, 2 * local + 1);
            localRect.Intersect(convRect);
            GetStats(&local_mean, &local_stdev, conv, localRect);

            // this is our measure of star intensity
            double h = (val - local_mean) / global_stdev;

            if (h < threshold)
            {
                //  Debug.AddLine(wxString::Format("AG: local max REJECT [%d, %d] PSF %.1f SNR %.1f", imgx, imgy, val, SNR));
                continue;
            }

            // coordinates on the original image
            int imgx = x * downsample + downsample / 2;
            int imgy = y * downsample + downsample / 2;

            stars.insert(Peak(imgx, imgy, h));
            if (stars.size() > TOP_N)
                stars.erase(stars.begin());
        }
    }

    for (std::set<Peak>::const_reverse_iterator it = stars.rbegin(); it != stars.rend(); ++it)
        Debug.AddLine("AutoFind: local max [%d, %d] %.1f", it->x, it->y, it->val);

    // merge stars that are very close into a single star
    {
        const int minlimitsq = 5 * 5;
    repeat:
        for (std::set<Peak>::const_iterator a = stars.begin(); a != stars.end(); ++a)
        {
            std::set<Peak>::const_iterator b = a;
            ++b;
            for (; b != stars.end(); ++b)
            {
                int dx = a->x - b->x;
                int dy = a->y - b->y;
                int d2 = dx * dx + dy * dy;
                if (d2 < minlimitsq)
                {
                    // very close, treat as single star
                    Debug.AddLine("AutoFind: merge [%d, %d] %.1f - [%d, %d] %.1f", a->x, a->y, a->val, b->x, b->y, b->val);
                    // erase the dimmer one
                    stars.erase(a);
                    goto repeat;
                }
            }
        }
    }

    // exclude stars that would fit within a single searchRegion box
    {
        // build a list of stars to be excluded
        std::set<int> to_erase;
        const int extra = 5; // extra safety margin
        const int fullw = searchRegion + extra;
        for (std::set<Peak>::const_iterator a = stars.begin(); a != stars.end(); ++a)
        {
            std::set<Peak>::const_iterator b = a;
            ++b;
            for (; b != stars.end(); ++b)
            {
                int dx = abs(a->x - b->x);
                int dy = abs(a->y - b->y);
                if (dx <= fullw && dy <= fullw)
                {
                    // stars closer than search region, exclude them both
                    // but do not let a very dim star eliminate a very bright star
                    if (b->val / a->val >= 5.0)
                    {
                        Debug.AddLine("AutoFind: close dim-bright [%d, %d] %.1f - [%d, %d] %.1f", a->x, a->y, a->val, b->x, b->y, b->val);
                    }
                    else
                    {
                        Debug.AddLine("AutoFind: too close [%d, %d] %.1f - [%d, %d] %.1f", a->x, a->y, a->val, b->x, b->y, b->val);
                        to_erase.insert(std::distance(stars.begin(), a));
                        to_erase.insert(std::distance(stars.begin(), b));
                    }
                }
            }
        }
        RemoveItems(stars, to_erase);
    }

    // exclude stars too close to the edge
    {
        enum { MIN_EDGE_DIST = 40 };
        int edgeDist = MIN_EDGE_DIST + extraEdgeAllowance;

        std::set<Peak>::iterator it = stars.begin();
        while (it != stars.end())
        {
            std::set<Peak>::iterator next = it;
            ++next;
            if (it->x <= edgeDist || it->x >= image.Size.GetWidth() - edgeDist ||
                it->y <= edgeDist || it->y >= image.Size.GetHeight() - edgeDist)
            {
                Debug.AddLine("AutoFind: too close to edge [%d, %d] %.1f", it->x, it->y, it->val);
                stars.erase(it);
            }
            it = next;
        }
    }

    // At first I tried running Star::Find on the survivors to find the best
    // star. This had the unfortunate effect of locating hot pixels which
    // the psf convolution so nicely avoids. So, don't do that!  -ag

    // find the brightest non-saturated star. If no non-saturated stars, settle for a saturated star.
    bool allowSaturated = false;
    while (true)
    {
        Debug.AddLine("AutoSelect: finding best star allowSaturated = %d", allowSaturated);

        for (std::set<Peak>::reverse_iterator it = stars.rbegin(); it != stars.rend(); ++it)
        {
            Star tmp;
            tmp.Find(&image, searchRegion, it->x, it->y, FIND_CENTROID);
            if (tmp.WasFound())
            {
                if (tmp.GetError() == STAR_SATURATED && !allowSaturated)
                {
                    Debug.AddLine("Autofind: star saturated [%d, %d] %.1f Mass %.f SNR %.1f", it->x, it->y, it->val, tmp.Mass, tmp.SNR);
                    continue;
                }
                SetXY(it->x, it->y);
                Debug.AddLine("Autofind returns star at [%d, %d] %.1f Mass %.f SNR %.1f", it->x, it->y, it->val, tmp.Mass, tmp.SNR);
                return true;
            }
        }

        if (allowSaturated)
            break; // no stars found

        Debug.AddLine("AutoFind: could not find a non-saturated star!");

        allowSaturated = true;
    }

    Debug.AddLine("Autofind: no star found");
    return false;
}
Пример #13
0
bool Star::AutoFind(const usImage& image, int extraEdgeAllowance, int searchRegion)
{
    if (!image.Subframe.IsEmpty())
    {
        Debug.AddLine("Autofind called on subframe, returning error");
        return false; // not found
    }

    wxBusyCursor busy;

    Debug.Write(wxString::Format("Star::AutoFind called with edgeAllowance = %d searchRegion = %d\n", extraEdgeAllowance, searchRegion));

    // run a 3x3 median first to eliminate hot pixels
    usImage smoothed;
    smoothed.CopyFrom(image);
    Median3(smoothed);

    // convert to floating point
    FloatImg conv(smoothed);

    // downsample the source image
    const int downsample = 1;
    if (downsample > 1)
    {
        FloatImg tmp;
        Downsample(tmp, conv, downsample);
        conv.Swap(tmp);
    }

    // run the PSF convolution
    {
        FloatImg tmp;
        psf_conv(tmp, conv);
        conv.Swap(tmp);
    }

    enum { CONV_RADIUS = 4 };
    int dw = conv.Size.GetWidth();      // width of the downsampled image
    int dh = conv.Size.GetHeight();     // height of the downsampled image
    wxRect convRect(CONV_RADIUS, CONV_RADIUS, dw - 2 * CONV_RADIUS, dh - 2 * CONV_RADIUS);  // region containing valid data

    SaveImage(conv, "PHD2_AutoFind.fit");

    enum { TOP_N = 100 };  // keep track of the brightest stars
    std::set<Peak> stars;  // sorted by ascending intensity

    double global_mean, global_stdev;
    GetStats(&global_mean, &global_stdev, conv, convRect);

    Debug.Write(wxString::Format("AutoFind: global mean = %.1f, stdev %.1f\n", global_mean, global_stdev));

    const double threshold = 0.1;
    Debug.Write(wxString::Format("AutoFind: using threshold = %.1f\n", threshold));

    // find each local maximum
    int srch = 4;
    for (int y = convRect.GetTop() + srch; y <= convRect.GetBottom() - srch; y++)
    {
        for (int x = convRect.GetLeft() + srch; x <= convRect.GetRight() - srch; x++)
        {
            float val = conv.px[dw * y + x];
            bool ismax = false;
            if (val > 0.0)
            {
                ismax = true;
                for (int j = -srch; j <= srch; j++)
                {
                    for (int i = -srch; i <= srch; i++)
                    {
                        if (i == 0 && j == 0)
                            continue;
                        if (conv.px[dw * (y + j) + (x + i)] > val)
                        {
                            ismax = false;
                            break;
                        }
                    }
                }
            }
            if (!ismax)
                continue;

            // compare local maximum to mean value of surrounding pixels
            const int local = 7;
            double local_mean, local_stdev;
            wxRect localRect(x - local, y - local, 2 * local + 1, 2 * local + 1);
            localRect.Intersect(convRect);
            GetStats(&local_mean, &local_stdev, conv, localRect);

            // this is our measure of star intensity
            double h = (val - local_mean) / global_stdev;

            if (h < threshold)
            {
                //  Debug.Write(wxString::Format("AG: local max REJECT [%d, %d] PSF %.1f SNR %.1f\n", imgx, imgy, val, SNR));
                continue;
            }

            // coordinates on the original image
            int imgx = x * downsample + downsample / 2;
            int imgy = y * downsample + downsample / 2;

            stars.insert(Peak(imgx, imgy, h));
            if (stars.size() > TOP_N)
                stars.erase(stars.begin());
        }
    }

    for (std::set<Peak>::const_reverse_iterator it = stars.rbegin(); it != stars.rend(); ++it)
        Debug.Write(wxString::Format("AutoFind: local max [%d, %d] %.1f\n", it->x, it->y, it->val));

    // merge stars that are very close into a single star
    {
        const int minlimitsq = 5 * 5;
    repeat:
        for (std::set<Peak>::const_iterator a = stars.begin(); a != stars.end(); ++a)
        {
            std::set<Peak>::const_iterator b = a;
            ++b;
            for (; b != stars.end(); ++b)
            {
                int dx = a->x - b->x;
                int dy = a->y - b->y;
                int d2 = dx * dx + dy * dy;
                if (d2 < minlimitsq)
                {
                    // very close, treat as single star
                    Debug.Write(wxString::Format("AutoFind: merge [%d, %d] %.1f - [%d, %d] %.1f\n", a->x, a->y, a->val, b->x, b->y, b->val));
                    // erase the dimmer one
                    stars.erase(a);
                    goto repeat;
                }
            }
        }
    }

    // exclude stars that would fit within a single searchRegion box
    {
        // build a list of stars to be excluded
        std::set<int> to_erase;
        const int extra = 5; // extra safety margin
        const int fullw = searchRegion + extra;
        for (std::set<Peak>::const_iterator a = stars.begin(); a != stars.end(); ++a)
        {
            std::set<Peak>::const_iterator b = a;
            ++b;
            for (; b != stars.end(); ++b)
            {
                int dx = abs(a->x - b->x);
                int dy = abs(a->y - b->y);
                if (dx <= fullw && dy <= fullw)
                {
                    // stars closer than search region, exclude them both
                    // but do not let a very dim star eliminate a very bright star
                    if (b->val / a->val >= 5.0)
                    {
                        Debug.Write(wxString::Format("AutoFind: close dim-bright [%d, %d] %.1f - [%d, %d] %.1f\n", a->x, a->y, a->val, b->x, b->y, b->val));
                    }
                    else
                    {
                        Debug.Write(wxString::Format("AutoFind: too close [%d, %d] %.1f - [%d, %d] %.1f\n", a->x, a->y, a->val, b->x, b->y, b->val));
                        to_erase.insert(std::distance(stars.begin(), a));
                        to_erase.insert(std::distance(stars.begin(), b));
                    }
                }
            }
        }
        RemoveItems(stars, to_erase);
    }

    // exclude stars too close to the edge
    {
        enum { MIN_EDGE_DIST = 40 };
        int edgeDist = MIN_EDGE_DIST + extraEdgeAllowance;

        std::set<Peak>::iterator it = stars.begin();
        while (it != stars.end())
        {
            std::set<Peak>::iterator next = it;
            ++next;
            if (it->x <= edgeDist || it->x >= image.Size.GetWidth() - edgeDist ||
                it->y <= edgeDist || it->y >= image.Size.GetHeight() - edgeDist)
            {
                Debug.Write(wxString::Format("AutoFind: too close to edge [%d, %d] %.1f\n", it->x, it->y, it->val));
                stars.erase(it);
            }
            it = next;
        }
    }

    // At first I tried running Star::Find on the survivors to find the best
    // star. This had the unfortunate effect of locating hot pixels which
    // the psf convolution so nicely avoids. So, don't do that!  -ag

    // try to identify the saturation point

    //  first, find the peak pixel overall
    unsigned short maxVal = 0;
    for (unsigned int i = 0; i < image.NPixels; i++)
        if (image.ImageData[i] > maxVal)
            maxVal = image.ImageData[i];

    // next see if any of the stars has a flat-top
    bool foundSaturated = false;
    for (std::set<Peak>::reverse_iterator it = stars.rbegin(); it != stars.rend(); ++it)
    {
        Star tmp;
        tmp.Find(&image, searchRegion, it->x, it->y, FIND_CENTROID);
        if (tmp.WasFound() && tmp.GetError() == STAR_SATURATED)
        {
            if ((maxVal - tmp.PeakVal) * 255U > maxVal)
            {
                // false positive saturation, flat top but below maxVal
                Debug.Write(wxString::Format("AutoSelect: false positive saturation peak = %hu, max = %hu\n", tmp.PeakVal, maxVal));
            }
            else
            {
                // a saturated star was found
                foundSaturated = true;
                break;
            }
        }
    }

    unsigned int sat_level; // saturation level, including pedestal
    if (foundSaturated)
    {
        // use the peak overall pixel value as the saturation limit
        Debug.Write(wxString::Format("AutoSelect: using saturation level peakVal = %hu\n", maxVal));
        sat_level = maxVal; // includes pedestal
    }
    else
    {
        // no staurated stars found, can't make any assumption about whether the max val is saturated

        Debug.Write(wxString::Format("AutoSelect: using saturation level from BPP %u and pedestal %hu\n",
            image.BitsPerPixel, image.Pedestal));

        sat_level = ((1U << image.BitsPerPixel) - 1) + image.Pedestal;
        if (sat_level > 65535)
            sat_level = 65535;
    }
    unsigned int diff = sat_level > image.Pedestal ? sat_level - image.Pedestal : 0U;
    // "near-saturation" threshold at 90% saturation
    unsigned short sat_thresh = (unsigned short)((unsigned int) image.Pedestal + 9 * diff / 10);

    Debug.Write(wxString::Format("AutoSelect: BPP = %u, saturation at %u, pedestal %hu, thresh = %hu\n",
        image.BitsPerPixel, sat_level, image.Pedestal, sat_thresh));

    // Final star selection
    //   pass 1: find brightest star with peak value < 90% saturation AND SNR > 6
    //       this pass will reject saturated and nearly-saturated stars
    //   pass 2: find brightest non-saturated star
    //   pass 3: find brightest star, even if saturated

    for (int pass = 1; pass <= 3; pass++)
    {
        Debug.Write(wxString::Format("AutoSelect: finding best star pass %d\n", pass));

        for (std::set<Peak>::reverse_iterator it = stars.rbegin(); it != stars.rend(); ++it)
        {
            Star tmp;
            tmp.Find(&image, searchRegion, it->x, it->y, FIND_CENTROID);
            if (tmp.WasFound())
            {
                if (pass == 1)
                {
                    if (tmp.PeakVal > sat_thresh)
                    {
                        Debug.Write(wxString::Format("Autofind: near-saturated [%d, %d] %.1f Mass %.f SNR %.1f Peak %hu\n", it->x, it->y, it->val, tmp.Mass, tmp.SNR, tmp.PeakVal));
                        continue;
                    }
                    if (tmp.GetError() == STAR_SATURATED || tmp.SNR < 6.0)
                        continue;
                }
                else if (pass == 2)
                {
                    if (tmp.GetError() == STAR_SATURATED)
                    {
                        Debug.Write(wxString::Format("Autofind: star saturated [%d, %d] %.1f Mass %.f SNR %.1f\n", it->x, it->y, it->val, tmp.Mass, tmp.SNR));
                        continue;
                    }
                }

                // star accepted
                SetXY(it->x, it->y);
                Debug.Write(wxString::Format("Autofind returns star at [%d, %d] %.1f Mass %.f SNR %.1f\n", it->x, it->y, it->val, tmp.Mass, tmp.SNR));
                return true;
            }
        }

        if (pass == 1)
            Debug.Write("AutoFind: could not find a star on Pass 1\n");
        else if (pass == 2)
            Debug.Write("AutoFind: could not find a non-saturated star!\n");
    }

    Debug.Write("Autofind: no star found\n");
    return false;
}