I am trying to simulate the Adobe Photoshop's blending mode - Darken.
I understand the basic Darken principle : Darken(a,b) = min(a,b). E.g.:
Darken( (.2, .3, .8), (.5, .1, .4) ) = (.2, .1, .4) // (r, g, b)
But I have transparency in it. In Photoshop, Darken works this way:
Darken( (0, 1, 0, .5), (0, 0, 0, .5) ) = (0, .3, 0, .75) // (r, g, b, a)
Darkening green over black is green. I see, that output alpha is computed in classic way: ao = aa + ab * (1-aa). Do you know how the other values are computed?
BTW. Darken mode is commutative.
So finally I found it out.
In Darken mode, composition is the same as in Normal mode, but if back channel is darker, front-back channels are flipped.
For each channel:
Darken(a, b) = a < b ? Norm(a, b) : Norm(b, a);
So in my top example, for green and alpha:
Darken( (1, .5), (0, .5) ) = Norm( (0, .5), (1, .5) )
alpha = (.5 + .5*(1-.5)) = .75
green = (0*.5 + 1*.5*(1-.5)) / .75 = (0 + .25) / .75 = 0.333333
Well, it's slightly more involved, actually. There are other, similar questions on here, which answer this. The best one to use is probably the aptly named:
Algorithm for Additive Color Mixing for RGB Values
Related
How can I draw a rectangle that has a color with an alpha?
I have:
windowSurface = pygame.display.set_mode((1000, 750), pygame.DOUBLEBUF)
pygame.draw.rect(windowSurface, pygame.Color(255, 255, 255, 128), pygame.Rect(0, 0, 1000, 750))
But I want the white rectangle to be 50% transparent, but the alpha value doesn't appear to be working.
pygame.draw functions will not draw with alpha. The documentation says:
Most of the arguments accept a color argument that is an RGB triplet. These can also accept an RGBA quadruplet. The alpha value will be written directly into the Surface if it contains pixel alphas, but the draw function will not draw transparently.
What you can do is create a second surface and then blit it to the screen. Blitting will do alpha blending and color keys. Also, you can specify alpha at the surface level (faster and less memory) or at the pixel level (slower but more precise). You can do either:
s = pygame.Surface((1000,750)) # the size of your rect
s.set_alpha(128) # alpha level
s.fill((255,255,255)) # this fills the entire surface
windowSurface.blit(s, (0,0)) # (0,0) are the top-left coordinates
or,
s = pygame.Surface((1000,750), pygame.SRCALPHA) # per-pixel alpha
s.fill((255,255,255,128)) # notice the alpha value in the color
windowSurface.blit(s, (0,0))
Keep in mind in the first case, that anything else you draw to s will get blitted with the alpha value you specify. So if you're using this to draw overlay controls for example, you might be better off using the second alternative.
Also, consider using pygame.HWSURFACE to create the surface hardware-accelerated.
Check the Surface docs at the pygame site, especially the intro.
Unfortunately there is no good way to draw a transparent shape. See pygame.draw module:
A color's alpha value will be written directly into the surface [...], but the draw function will not draw transparently.
So you can't draw transparent shapes directly with the 'pygame.draw' module. The 'pygame.draw' module does not blend the shape with the target surface. You have to draw the shape on a surface (with RGBA format). Then you can blit (and thus blend) this surface. See Draw a transparent rectangles and polygons in pygame. Hence you need to do a workaround:
Create a pygame.Surface object with a per-pixel alpha format large enough to cover the shape.
Draw the shape on the _Surface.
Blend the Surface with the target Surface. blit() by default blends 2 Surfaces
For example 3 functions, which can draw transparent rectangles, circles and polygons:
def draw_rect_alpha(surface, color, rect):
shape_surf = pygame.Surface(pygame.Rect(rect).size, pygame.SRCALPHA)
pygame.draw.rect(shape_surf, color, shape_surf.get_rect())
surface.blit(shape_surf, rect)
def draw_circle_alpha(surface, color, center, radius):
target_rect = pygame.Rect(center, (0, 0)).inflate((radius * 2, radius * 2))
shape_surf = pygame.Surface(target_rect.size, pygame.SRCALPHA)
pygame.draw.circle(shape_surf, color, (radius, radius), radius)
surface.blit(shape_surf, target_rect)
def draw_polygon_alpha(surface, color, points):
lx, ly = zip(*points)
min_x, min_y, max_x, max_y = min(lx), min(ly), max(lx), max(ly)
target_rect = pygame.Rect(min_x, min_y, max_x - min_x, max_y - min_y)
shape_surf = pygame.Surface(target_rect.size, pygame.SRCALPHA)
pygame.draw.polygon(shape_surf, color, [(x - min_x, y - min_y) for x, y in points])
surface.blit(shape_surf, target_rect)
Minimal example: repl.it/#Rabbid76/PyGame-TransparentShapes
import pygame
def draw_rect_alpha(surface, color, rect):
shape_surf = pygame.Surface(pygame.Rect(rect).size, pygame.SRCALPHA)
pygame.draw.rect(shape_surf, color, shape_surf.get_rect())
surface.blit(shape_surf, rect)
def draw_circle_alpha(surface, color, center, radius):
target_rect = pygame.Rect(center, (0, 0)).inflate((radius * 2, radius * 2))
shape_surf = pygame.Surface(target_rect.size, pygame.SRCALPHA)
pygame.draw.circle(shape_surf, color, (radius, radius), radius)
surface.blit(shape_surf, target_rect)
def draw_polygon_alpha(surface, color, points):
lx, ly = zip(*points)
min_x, min_y, max_x, max_y = min(lx), min(ly), max(lx), max(ly)
target_rect = pygame.Rect(min_x, min_y, max_x - min_x, max_y - min_y)
shape_surf = pygame.Surface(target_rect.size, pygame.SRCALPHA)
pygame.draw.polygon(shape_surf, color, [(x - min_x, y - min_y) for x, y in points])
surface.blit(shape_surf, target_rect)
pygame.init()
window = pygame.display.set_mode((250, 250))
clock = pygame.time.Clock()
background = pygame.Surface(window.get_size())
ts, w, h, c1, c2 = 50, *window.get_size(), (160, 160, 160), (192, 192, 192)
tiles = [((x*ts, y*ts, ts, ts), c1 if (x+y) % 2 == 0 else c2) for x in range((w+ts-1)//ts) for y in range((h+ts-1)//ts)]
for rect, color in tiles:
pygame.draw.rect(background, color, rect)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
window.blit(background, (0, 0))
draw_rect_alpha(window, (0, 0, 255, 127), (55, 90, 140, 140))
draw_circle_alpha(window, (255, 0, 0, 127), (150, 100), 80)
draw_polygon_alpha(window, (255, 255, 0, 127),
[(100, 10), (100 + 0.8660 * 90, 145), (100 - 0.8660 * 90, 145)])
pygame.display.flip()
pygame.quit()
exit()
the most i can do to help you is to show you how to draw a rectangle that is not filled in.
the line for the rectangle is:
pygame.draw.rect(surface, [255, 0, 0], [50, 50, 90, 180], 1)
the "1" means that it is not filled in
A way to do this is to instead of drawing a rectangle with pygame, you could create an image of a transparent rectangle by using a drawing program such as paint.net or Fire Alpaca.
I have images with eight blue and eight red circles.
I want to find the center position ((x, y) in pixels) of each circle and I also want to know whether the circle is red or blue.
The circles can have a slight variation in diameter.
It feels like this would not be so hard to solve, but it's to hard for me ...
I tried using OpenCV and Template Matching by following a tutorial. It finds all the circles but I don't know how to pinpoint the center or pick the circle-color.
It also seems to draw a lot more than 1 rectangle per circle.
img_rgb = cv2.imread('images/img.png')
img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
template = cv2.imread('red.png',0)
w, h = template.shape[::-1]
res = cv2.matchTemplate(img_gray,template,cv2.TM_CCOEFF_NORMED)
threshold = 0.66
loc = np.where( res >= threshold)
for pt in zip(*loc[::-1]):
cv2.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (0,0,255), 1)
I also gave Hough Circles a try. But without any result at all. Maybe my circles are too small to be true circles, with the pixels showing.
Is Template Matching the right approach, even if I don't get it to work all the way, or can it be done more easily another way?
Any help would be much appreciated.
Thanks Martin
I solved it myself with using findContours.
image = cv2.imread('images/img.png')
original = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
thresh = cv2.threshold(blurred, 80, 255, cv2.THRESH_BINARY)[1]
cnts = cv2.findContours(thresh.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if imutils.is_cv2() else cnts[1]
coordinates = []
for c in cnts:
M = cv2.moments(c)
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
#cv2.circle(image, (cX, cY), 1, (255, 255, 255), -1)
coordinates.append([cX,cY])
I'm trying to obtain a 4x4 projection matrix that transforms a point in the world to the display coordinates.
Having a pixel (x, y) and the corresponding z-value (from the zbuffer), I obtain its 3D world coordinates with vtkWorldPointPicker class. Let's denote the result by x.
According to documentation, I can compute the view coordinates of the world point by applying the matrix GetCompositeProjectionTransformMatrix to x. Next, I'm using the transformation from the view to the initial display coordinates by using the code found in vtkViewport::ViewToDisplay (*):
dx = (v[0] + 1.0) * (sizex*(v[2]-v[0])) / 2.0 + sizex*v[0];
dy = (v[1] + 1.0) * (sizey*(v[3]-v[1])) / 2.0 + sizey*v[1];
where sizex and sizey are the width and height of the image in pixels, and v are the computed view coordinates.
Unfortunately, the values I get back do not match the original:
display [0, 0, 0.716656] // x,y-pixel coordinates and the zbuffer
x = [0.0255492, -0.0392383, 0.00854707] // world coordinates (using vtkWorldPointPicker)
// camera->GetCompositeProjectionTransformMatrix
P = [
-1.84177 0 0 0
0 1.20317 1.39445 0
0 -757.134 653.275 -9.9991
0 -0.757126 0.653268 0 ]
v = [-0.0470559, -0.0352919, 25.2931, 0.0352919] // P*x
a = [7697.18, -0.597848] // using (*)
Is this approach (in general) correct, or is there a more conventional way to do this? Thanks for any help.
Edit: the provided snippet from vtkViewport::ViewToDisplay is incorrect. It should read:
dx = (v[0] + 1.0) * (sizex*(vp[2]-vp[0])) / 2.0 + sizex*vp[0];
dy = (v[1] + 1.0) * (sizey*(vp[3]-vp[1])) / 2.0 + sizey*vp[1];
Note, that v refers to the normalised view coordinates, vp is the viewport (by default, vp := [0, 0, 1, 1])!
The conversion is indeed valid, although there might be built-in ways to obtain the final matrix.
Assuming only one (default) viewport is used, the matrix converting the view into display coordinates is:
M = [X/2, 0, 0, X/2,
0, Y/2, 0, Y/2,
0, 0, 1, 0,
0, 0, 0, 1]
where X and Y is the width and height of the image in pixels.
Hence, given a point x in the world coordinates, the display coordinates in homogeneous form are:
c = M * P * x;
where P is the CompositeProjectionTransformMatrix. After normalising (c[i] /= c[3], i = 0,1,2) we arrive at the original pixel values.
I was playing around with some different image formats and ran across something I found odd. When converting from RGB to YCbCr and then back to RGB, the results are very similar to what I started with (the difference in pixels values is almost always less than 4). However, when I convert from YCbCr to RGB and then back to YCbCr, I often get vastly different values. Sometimes a values will differ by over 40.
I'm not sure why this is. I was under the impression that the colors which could be expressed through YCbCr were a subset of those in RGB, but it looks like this is completely wrong. Is there some known subset of colors in YCbCr that can be converted to RGB and then back to their original values?
The code I'm using to convert (based on this site):
def yuv2rgb(yuv):
ret = []
for rows in yuv:
row = []
for y, u, v in rows:
c = y - 16
d = u - 128
e = v - 128
r = clamp(1.164*c + 1.596*e, 16, 235)
g = clamp(1.164*c - 0.392*d - 0.813*e, 16, 240)
b = clamp(1.164*c + 2.017*d , 16, 240)
row.append([r, g, b])
ret.append(row)
return ret
def rgb2yuv(rgb):
ret = []
for rows in rgb:
row = []
for r, g, b in rows:
y = int( 0.257*r + 0.504*g + 0.098*b + 16)
u = int(-0.148*r - 0.291*g + 0.439*b + 128)
v = int( 0.439*r - 0.368*g - 0.071*b + 128)
row.append([y, u, v])
ret.append(row)
return ret
EDIT:
I created a rudimentary 3D graph of this issue. All the points are spots with a value difference less than 10. It makes a pretty interesting shape. X is Cb, Y is Cr, and Z is Y.
No, it is not, at all. [All disscused above is for 8 bit.] And it is obvious in the case of Full range R'G'B' to limited range YCbCr (there is just no bijection). For example, you can test it here:
https://res18h39.netlify.app/color
Full range R'G'B' values 238, 77, 45 are encoded into limited YCbCr with BT.601 matrix: you will get limited range 120, 90, 201 after school rounding, but if you will round it you will get 238, 77, 44 back in R'G'B'. And 238, 77, 44 value will go to the same value. Oopsie. Here it is: game over.
In the case with full range RGB to full range YCbCr... There are some values in YCbCr that will be negative R', G', B'. (For example in limited range YCbCr BT.709 values 139, 151, 24 will be RGB -21, 182, 181, just convert for full range YCbCr.) So again, no bijection.
Next, limited range R'G'B' to limited range YCbCr... Again, no bijection. Black in YCbCr is actually 16, 128, 128 and only this. All other 16, x, y are not allowed [they are in xvYCC, which is non-standard], while they are in R, G, B, and all 235, 128, 128 the same. And the previous negative R', G', B' also applies, of course.
And with limited range to full range, I do not know.
As I said in my comment, your first problem is that you're using y rather than c for calculations inside the loop in yuv2rgb.
The second problem is that you're clamping the RGB values to the wrong range: RGB should be 0..255.
The RGB calculations should look like this:
r = clamp(1.164*c + 1.596*e, 0, 255)
g = clamp(1.164*c - 0.392*d - 0.813*e, 0, 255)
b = clamp(1.164*c + 2.017*d , 0, 255)
As far as I know you should be able to convert from and to both formats with minimal precision loss.
The site you have mentioned has another set of conversion formulas called "RGB to full-range YCbCr" and "Full-range YCbCr to RGB", I believe these are the ones you should use and I think it should enable you to convert forward and back without any problems.
EDIT:
Since those formulas haven't worked for you, I'll share the formulas that I use for conversion between RGB and YUV in android:
R = clamp(1 * Y + 0 * (U - 128) + 1.13983 * (V - 128), 0, 255);
G = clamp(1 * Y + -0.39465 * (U - 128) + -0.5806 * (V - 128), 0, 255);
B = clamp(1 * Y + 2.03211 * (U - 128) + 0 * (V - 128), 0, 255);
Y = clamp(0.299 * R + 0.587 * G + 0.114 * B, 0, 255);
U = clamp(-0.14713 * R + -0.28886 * G + 0.436 * B + 128, 0, 255);
V = clamp(0.615 * R + -0.51499 * G + -0.10001 * B + 128, 0, 255);
I've just tried and it seems to work back and forth. Notice the sums and subtractions of 128, because this YUV representation is composed of unsigned byte ranges (0..255) and so is RGB (as usual), so if you really need a (16..235) and (16..240) ranges for your YCbCr you might need another formula.
Suppose we have a set of points with the restriction that for each point all coordinates are non-negative, and the sum of coordinates is equal to 1. This restricts points to lie in a 3-dimensional simplex so it makes sense to try to map it back into 3 dimensional space for visualization.
The map I'm looking for would take extreme points (1,0,0,0),(0,1,0,0),(0,0,1,0) and (0,0,0,1) to vertices of "nicely positioned" regular tetrahedron. In particular, center of the tetrahedron will be at the origin, one vertex would lie on the z axis, one face to parallel to x,y plane, and one edge to be parallel to x axis.
Here's code that does similar thing for points in 3 dimensions, but it doesn't seem obvious how to extend it to 4. Basically I'm looking for 4-d equivalents of functions tosimplex (which takes 4 dimensions into 3) and it's inverse fromsimplex
A = Sqrt[2/3] {Cos[#], Sin[#], Sqrt[1/2]} & /#
Table[Pi/2 + 2 Pi/3 + 2 k Pi/3, {k, 0, 2}] // Transpose;
B = Inverse[A];
tosimplex[{x_, y_, z_}] := Most[A.{x, y, z}];
fromsimplex[{u_, v_}] := B.{u, v, Sqrt[1/3]};
(* checks *)
extreme = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}};
Graphics[Polygon[tosimplex /# extreme]]
fromsimplex[tosimplex[#]] == # & /# extreme
Answer:
straightforward reformulation of deinst's answer in terms of matrices gives following. (1/sqrt[4] comes up as 4th coordinate because it's the distance to simplex center)
A = Transpose[{{-(1/2), -(1/(2 Sqrt[3])), -(1/(2 Sqrt[6])),
1/Sqrt[4]}, {1/2, -(1/(2 Sqrt[3])), -(1/(2 Sqrt[6])),
1/Sqrt[4]}, {0, -(1/(2 Sqrt[3])) + Sqrt[3]/2, -(1/(2 Sqrt[6])),
1/Sqrt[4]}, {0, 0, Sqrt[2/3] - 1/(2 Sqrt[6]), 1/Sqrt[4]}}];
B = Inverse[A];
tosimplex[{x_, y_, z_, w_}] := Most[A.{x, y, z, w}];
fromsimplex[{t_, u_, v_}] := B.{t, u, v, 1/Sqrt[4]};
(* Checks *)
extreme = Table[Array[Boole[# == i] &, 4], {i, 1, 4}];
Graphics3D[Sphere[tosimplex[#], .1] & /# extreme]
fromsimplex[tosimplex[#]] == # & /# extreme
You want
(1,0,0,0) -> (0,0,0)
(0,1,0,0) -> (1,0,0)
(0,0,1,0) -> (1/2,sqrt(3)/2,0)
(0,0,0,1) -> (1/2,sqrt(3)/6,sqrt(6)/3))
And it is a linear transformation so you transform
(x,y,z,w) - > (y + 1/2 * (z + w), sqrt(3) * (z / 2 + w / 6), sqrt(6) * w / 3)
Edit You want the center at the origin -- just subtract the average of the four points. Sorry
(1/2, sqrt(3)/6, sqrt(6) / 12)
One possibility:
Generate four (non-orthoganal) 3-vectors, \vec{v}_i from the center of the tetrahedron toward each vertex.
For each four position x = (x_1 .. x_4) form the vector sum \Sum_i x_i*\vec{v}_i.
Of course this mapping is not unique in general, but you condition that the x_i's sum to 1 constrains things.