JUCE 8 preview: borders around opaque components

This issue only manifests itself with the Direct2D renderer in JUCE 8.
When resizing the window (and applying an affineTransform::scale to its components) I get visible borders around opaque components, showing the background their grandparent.

opaque

Here is the demonstration code:

class TestComponent : public juce::Component {
public:
	TestComponent() {
		addAndMakeVisible(mFillAllOpaque);
		addAndMakeVisible(mFillAllNonOpaque);
	}

	void resized() override {
		auto bounds = getLocalBounds();
		mFillAllOpaque.setBounds(bounds.removeFromTop(170).reduced(20));
		mFillAllNonOpaque.setBounds(bounds.removeFromTop(170).reduced(20));
	}

	void paint(juce::Graphics& g) override {
		g.fillAll(juce::Colours::white);
	}

private:
	struct FillAll : public juce::Component {
		FillAll(bool opaque) {
			setOpaque(opaque);
		}
		void paint(juce::Graphics& g) override {
			g.fillAll(juce::Colour::greyLevel(.95f));
			auto text = juce::String("setOpaque(") + (isOpaque() ? "true" : "false") + ")";
			g.drawText(text, getLocalBounds().toFloat(), juce::Justification::centred);
		}
	};

	FillAll mFillAllOpaque{ true }, mFillAllNonOpaque{ false };

	JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TestComponent)
};

class MainComponent  : public juce::Component {
public:
	MainComponent() {

		//juce::Timer::callAfterDelay(0, [this] {
		//	auto* peer = getPeer();
		//	jassert(peer);
		//	if (peer) peer->setCurrentRenderingEngine(0);
		//});

		setSize(referenceWidth, referenceWidth);
		addAndMakeVisible(component);
		addAndMakeVisible(zoomLabel);
		refreshZoomLabel();
	}

	void paint(juce::Graphics& g) override {
		g.fillAll(juce::Colours::black);
	}

	void resized() override {
		refreshZoomLabel();
		zoomLabel.setBounds(getLocalBounds().removeFromTop(30));
		component.setBounds(0, 0, referenceWidth, referenceWidth);
		component.setTransform(juce::AffineTransform::scale(getScale()).followedBy(juce::AffineTransform::translation(0,30)));
	}

private:
	void refreshZoomLabel() {
		zoomLabel.setText("zoom: " + juce::String(juce::roundToInt(100 * getScale())) + "%", juce::dontSendNotification);
	}

	float getScale() {
		return (float)getWidth() / referenceWidth;
	}

	juce::Label zoomLabel;
	TestComponent component;

	int referenceWidth = 400;

	JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
};

This problem is not related to JUCE 8. It is discussed several times in this Forum.

Here an example:

1 Like

The whole geometry was changed to float in JUCE 8, so it happens differently, and it is probably a good opportunity to solve this.

What do you mean by that? JUCE7 also internally converted everything to float and then applied transforms, etc., so this is nothing new.

There were a lot of differences between int and float rectangles.
See this recent topic: Int vs float rectangles with scaling: a proposal for Windows/MacOS consistency for JUCE 8

Now with JUCE 8 float and int rectangle do behave the same, including setBounds.

But Component size and position is still integer only i guess. Would be great if this could be solved. But I remember some discussions with Jules that this isn’t possible without image oversampling.
Opaque components can not have smooth anti aliasing transitions to the parent, because they are Opaque. They only fit, when they are aligned to pixels.

Try using a transform to position your component using floating-point coordinates:

Matt

Take a look at the thread I posted above, and try the example code in both JUCE 7 and JUCE 8 on windows.
Now int rectangles (including component bounds) do act exactly like float ones with identical values.

We need anti aliasing when components are not aligned to a pixel. For example when scaled. And this does not work with opaque components because they don’t support alpha values.

I’d love to have some notes from the JUCE team about the design decisions that took place here, and what the limitations are.
This was not part of JUCE 7 D2D, so I suppose this is the work of @reuk ?

We’ve just pushed a couple more fixes for this issue on the JUCE8 branch:

This wasn’t entirely true, it also manifested in the CoreGraphics renderer on macOS. It was not present in the software renderer or OpenGL renderer.

This is sort of true. The borders arise when two components join at a fractional pixel position. In this case, the edges of both components will be anti-aliased. The expected behaviour is that the background component is drawn in its original colour, and only the foreground component is anti-aliased.

Opaque components work by excluding the child area from the clip region when drawing the parent component. In the software renderer, the excluded area is always snapped ‘inwards’ to the closest integer size, which means that the background is not anti-aliased. This doesn’t matter in practice because the non-anti-aliased edge is completely hidden by the opaque foreground component.

The changes I linked above make this behaviour consistent in the CoreGraphics and Direct2D renderers. Please try out this change and let us know if you run into any new problems.

2 Likes

Great to hear that this is issue is finally fixed. I didn’t expect that after all those years :slight_smile: Looking forward to trying this.