Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement option to use Porter-Duffman src operator #193

Open
dkononovGm opened this issue Dec 7, 2022 · 16 comments
Open

Implement option to use Porter-Duffman src operator #193

dkononovGm opened this issue Dec 7, 2022 · 16 comments

Comments

@dkononovGm
Copy link

Hi! Probably this isn't issue, but browsing through your code I coudn't find any solution. The point is that I'm trying to draw a "map" where contours (or layers) have both humps and holes (see figs attached). And in some holes the bottom layer is zero (pixels must not be filled). But when I subsequently overlay the contours and add the zero contour to the top, I see not an empty hole but the last underlying contour with minimal, but non-zero value. Could you please give some advice, how I could solve this problem?

Thanks in advance,
Dmitry

frame_0

@tdewolff
Copy link
Owner

tdewolff commented Dec 7, 2022

Do you have a small example code that reproduces this?

@dkononovGm
Copy link
Author

Yes, here it is.

This is the main loop overlaping layers ordered by the area (larger areas first, smaller atop)

for _, rng := range contourPixelPoints {
		col := rng.Color
		ctx.SetFillColor(col)
		ctx.SetStrokeColor(col)
		ctx.SetStrokeWidth(0.0)

		line := pixelsToLine(rng.Line)
		//ctx.Style.FillRule = canvas.EvenOdd
		ctx.DrawPath(0, 0, line...)
	}

This the function constructing lines:

func pixelsToLine(pixelLine []LinePoint) []*canvas.Path {
	var line []*canvas.Path
	polyline := &canvas.Polyline{}
	for _, l := range pixelLine {
		polyline.Add(l.Px, l.Py)
	}
	line = append(line, polyline.Smoothen())
	return line
}

And this is the palette from which I choose the "col" (color) variable:

precipitationPalette := PaletteRangeColor{
		Ranges: [][]float64{{0.07, 0.1}, {0.1, 0.2}, {0.2, 0.3}, {0.3, 0.4}, {0.4, 0.5}, {0.5, 0.6}, {0.6, 0.7}, {0.7, 0.8}, {0.8, 0.9}, {0.9, 1}, {1, 2}, {2, 3}, {3, 4}, {4, 5}, {5, 6}, {6, 7}, {7, 9}, {9, 11}, {11, 13}, {13, 15}, {15, 20}, {20, 50}, {50, 70}, {70, 100}, {100, 120}, {120, 1000}}, // 1000 is an arbitrary high number
		Colors: []color.RGBA{{0x00, 0x00, 0x00, 0x00}, {220, 240, 255, 255}, {174, 220, 255, 255}, {128, 200, 255, 255}, {110, 193, 255, 255}, {91, 186, 255, 255}, {73, 179, 255, 255}, {55, 171, 255, 255}, {37, 164, 255, 255}, {18, 157, 255, 255}, {0, 150, 255, 255}, {0, 144, 245, 255}, {0, 138, 235, 255}, {0, 132, 226, 255}, {0, 127, 217, 255}, {0, 122, 208, 255}, {0, 100, 255, 255}, {0, 50, 255, 255}, {0, 38, 223, 255}, {0, 13, 160, 255}, {0, 0, 128, 255}, {0, 0, 96, 255}, {0, 0, 64, 255}, {0, 0, 32, 255}, {0, 0, 0, 255}, {0, 0, 0, 0}, {0, 0, 0, 0}},
	}

Please notice the first element in the color array. I do know that I have contours with the values {0.07, 0.1} that should correspond to transparent pixels and I know that some of these contours should be atop as shown in the picture in the first message. However I instead see only contours with the color {220, 240, 255, 255} (that is first below). If I substitute the first element in the color array with {0x00, 0x00, 0x00, 0xff}, I see black spots atop.

@tdewolff
Copy link
Owner

tdewolff commented Dec 11, 2022 via email

@dkononovGm
Copy link
Author

Just a quick idea, the rasterizer gives awkward results for open paths. Are you sure the paths are closed? You can close a polyline by appending the starting point at the end.

I have just checked the contours and all of them seem to be closed (see debug output attached).

Снимок экрана 2022-12-12 в 13 47 05

@dkononovGm
Copy link
Author

dkononovGm commented Dec 12, 2022

I have looked at your "draw" function and have a question:

// DrawPath draws a path at position (x,y) using the current draw state.
func (c *Context) DrawPath(x, y float64, paths ...*Path) {
	if !c.Style.HasFill() && !c.Style.HasStroke() {
		return
	}

	coord := c.coordView.Dot(Point{x, y})
	m := c.view.Translate(coord.X, coord.Y)
	for _, path := range paths {
		var dashes []float64
		path, dashes = path.checkDash(c.Style.DashOffset, c.Style.Dashes)
		if path.Empty() {
			continue
		}
		style := c.Style
		style.Dashes = dashes
		c.RenderPath(path, style, m)
	}
}

In the fist condition you exit from the function if !c.Style.HasFill(). As I understand, if the alpha = 0, HasFill = false and this contour won't be drawn, i.e. pixel values inside this contour won't be set to zero, but will retain the previous values (the values of the previous layer). Am I right? May it be the reason of the issue?

@tdewolff
Copy link
Owner

Drawing a shape is always blended with previously drawn shapes below. That is, drawing transparent pixels is like doing nothing, which is why HasFill returns false. If you want a pixel to remain transparent, never draw to it.

Would you be able to send me a small piece of working code that reproduces the issue? Then I can take a look at it.

@dkononovGm
Copy link
Author

dkononovGm commented Dec 13, 2022

I unfortunately cannot send a small piece of the code, since it's distributed over a number of files and functions. But I can try explaining what I want to have as a result with a simple illustration:

Снимок экрана 2022-12-13 в 15 07 38

I want to draw something like in the picture with the non-filled annulus in the center. Is it possible with your package?

@dkononovGm
Copy link
Author

dkononovGm commented Dec 13, 2022

I seem to have figured it out how to illustrate my question with a small piece of code.

Let us imagine that I have three circular contours as above. I draw them in series:

// bottom dark blue path
ctx.SetFillColor(color.Blue)
ctx.SetStrokeColor(color.Blue)
ctx.SetStrokeWidth(0.0)
line := formCircularContour(...)
ctx.DrawPath(0, 0, line...)

//middle cyan path (or light blue, I just do not remember how it is designated in the color list)
ctx.SetFillColor(color.Cyan)
ctx.SetStrokeColor(color.Cyan)
ctx.SetStrokeWidth(0.0)
line := formCircularContour(...)
ctx.DrawPath(0, 0, line...)

//upper path whose "color" I do not know. I want the pixels inside this path to be set default values {0,0,0,0} if it is possible
ctx.SetFillColor(?)
ctx.SetStrokeColor(?)
ctx.SetStrokeWidth(0.0)
line := formCircularContour(...)
ctx.DrawPath(0, 0, line...)

I of course do something like this within a loop. But the main idea is like above. So the question is if this way of drawing contours one on one is appropriate for having the picture above within the paradigm of your package, or I need to invent a different method.

@tdewolff
Copy link
Owner

tdewolff commented Dec 13, 2022

In general, the way drawing happens in most libraries (imitating what happens when you literally paint something) is that you can't remove painted pixels below, you can only over-paint with a new color. The rasterizer actually allows the Porter-Duffman operators Over and Src, where the first is used by this library, but it seems that the second would achieve what you need. The problem is that the other renderers (PDF, SVG, ...) would need to generate consistent results which I'm not sure they can and would involve some work.

One way to fix this in your case though would be to paint a subpath in the contrary direction (clockwise/counter clockwise) within the outer path. This only works when you're sure that the higher layer path is contained in the lower layer path. E.g.:

line0 := formCircularContour(...)
line1 := formCircularContour(...)
line2 := formCircularContour(...)

// bottom dark blue path
ctx.SetFillColor(color.Blue)
ctx.SetStrokeColor(color.Blue)
ctx.SetStrokeWidth(0.0)
ctx.DrawPath(0, 0, line0.Append(line1.Reverse()))

//middle cyan path (or light blue, I just do not remember how it is designated in the color list)
ctx.SetFillColor(color.Cyan)
ctx.SetStrokeColor(color.Cyan)
ctx.SetStrokeWidth(0.0)
ctx.DrawPath(0, 0, line1.Append(line2.Reverse()))

// NOT NEEDED
//upper path whose "color" I do not know. I want the pixels inside this path to be set default values {0,0,0,0} if it is possible
//ctx.SetFillColor(?)
//ctx.SetStrokeColor(?)
//ctx.SetStrokeWidth(0.0)
//ctx.DrawPath(0, 0, line2)

I'm working on path boolean operations as we speak, which would allow line1 = line1.Not(line0) instead, which results in the same thing. Anyways, I'll take a look at whether we could implement the Src blending mode for all renderers.

@tdewolff tdewolff mentioned this issue Dec 13, 2022
42 tasks
@dkononovGm
Copy link
Author

Oh, I see. Am I right that by adding a subpath you in fact form a "complex path" that is, in fact, a ring?

The problem is that I have both ascending and descending embedded contours. Will the idea you proposed for now work in the ascending order of layer values?

Thanks for your help. I'll try to implement this way for now and wait for updates

In general, the way drawing happens in most libraries (imitating what happens when you literally paint something) is that you can't remove painted pixels below, you can only over-paint with a new color. The rasterizer actually allows the Porter-Duffman operators Over and Src, where the first is used by this library, but it seems that the second would achieve what you need. The problem is that the other renderers (PDF, SVG, ...) would need to generate consistent results which I'm not sure they can and would involve some work.

One way to fix this in your case though would be to paint a subpath in the contrary direction (clockwise/counter clockwise) within the outer path. This only works when you're sure that the higher layer path is contained in the lower layer path. E.g.:

line0 := formCircularContour(...)
line1 := formCircularContour(...)
line2 := formCircularContour(...)

// bottom dark blue path
ctx.SetFillColor(color.Blue)
ctx.SetStrokeColor(color.Blue)
ctx.SetStrokeWidth(0.0)
ctx.DrawPath(0, 0, line0.Append(line1.Reverse()))

//middle cyan path (or light blue, I just do not remember how it is designated in the color list)
ctx.SetFillColor(color.Cyan)
ctx.SetStrokeColor(color.Cyan)
ctx.SetStrokeWidth(0.0)
ctx.DrawPath(0, 0, line1.Append(line2.Reverse()))

// NOT NEEDED
//upper path whose "color" I do not know. I want the pixels inside this path to be set default values {0,0,0,0} if it is possible
//ctx.SetFillColor(?)
//ctx.SetStrokeColor(?)
//ctx.SetStrokeWidth(0.0)
//ctx.DrawPath(0, 0, line2)

I'm working on path boolean operations as we speak, which would allow line1 = line1.Not(line0) instead, which results in the same thing. Anyways, I'll take a look at whether we could implement the Src blending mode for all renderers.

@dkononovGm
Copy link
Author

Oh. And I have one more question. Do this operation line1.Append(line2.Reverse()) connect the last point of the first contour with the first point of the next contour by MoveTo, or by LineTo?

@tdewolff
Copy link
Owner

Yes, it subtracts the inner path from the outer path. Appending literally appends the path and does no effort to join both paths, for that we have Join.

I'm not sure what the problem is with ascending/descending. From what I understand, you always need to draw from the bottom up, and from what I see is that each next layer is contained within the previous layer, right? That is, each layer is getting smaller and doesn't intersect with the layers below.

@dkononovGm
Copy link
Author

Oh. Almost, but my task is a little more complicated. I can easily have several smaller area contours within one big area contour and those smaller may have both larger and smaller values (see the initial picture in my first message of this issue). Well. I'll try to refactor my code to account for contour embedding, since now I only arrange contours by areas.

Yes, it subtracts the inner path from the outer path. Appending literally appends the path and does no effort to join both paths, for that we have Join.

I'm not sure what the problem is with ascending/descending. From what I understand, you always need to draw from the bottom up, and from what I see is that each next layer is contained within the previous layer, right? That is, each layer is getting smaller and doesn't intersect with the layers below.

@tdewolff
Copy link
Owner

Well, it depends on whichever value should be on top. I was assuming that darker blue should always be on top (higher value), but if this is not always the case the method above applies equally well. Just with the consideration that higher layers are contained within the lower ones (whether the value is higher/lower i.e. color more white or more blue doesn't matter). I hope that helps. Otherwise you could sort by value first?

@tdewolff
Copy link
Owner

@dkononovGm Is this still an issue?

@dkononovGm
Copy link
Author

@dkononovGm Is this still an issue?

Yes. I still hope that you'll be able to implement that P-D operator you mentioned. :)

@tdewolff tdewolff changed the title Add zero layer above all Implement options to use Porter-Duffman src operator Sep 13, 2023
@tdewolff tdewolff changed the title Implement options to use Porter-Duffman src operator Implement option to use Porter-Duffman src operator Sep 13, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants