quantization of color gradient with LIBGDX - libgdx

I am drawing a color gradient with rect of shaperenderer.
This produces a soft gradient.
Is there a way to quantize the colors. For example I like to have only 10 different colors between min Color and max Color.
Is this possible with predefined functionallity from LIBGDX without drawing this by self ?

One way to achieve this (if you can spare the CPU cycles) could be to hand-roll a version of ShapeRenderer::rect that does this by drawing several rects and interpolates between the four colors.
private static Color quantizedColor = new Color();
private static void rectQuantized(ShapeRenderer shapeRenderer, float x, float y, float width, float height, Color color1, Color color2, Color color3, Color color4, int steps) {
float dx = width / steps;
float dy = height / steps;
for(int sy = 0; sy < steps; ++sy) {
float py = (float)sy / (steps - 1);
for(int sx = 0; sx < steps; ++sx) {
float px = (float)sx / (steps - 1);
float minR = (1.0f - px) * color1.r + px * color2.r;
float maxR = (1.0f - px) * color4.r + px * color3.r;
float minG = (1.0f - px) * color1.g + px * color2.g;
float maxG = (1.0f - px) * color4.g + px * color3.g;
float minB = (1.0f - px) * color1.b + px * color2.b;
float maxB = (1.0f - px) * color4.b + px * color3.b;
float minA = (1.0f - px) * color1.a + px * color2.a;
float maxA = (1.0f - px) * color4.a + px * color3.a;
quantizedColor.set(minR + (maxR - minR) * py, minG + (maxG - minG) * py, minB + (maxB - minB) * py, minA + (maxA - minA) * py);
shapeRenderer.setColor(quantizedColor);
shapeRenderer.rect(x + dx * sx, y + dy * sy, dx, dy);
}
}
}
Using the code above with a 200x200 rect quantized to 16 steps it would look like this (comparison using the standard ShapeRenderer::rect call on the left)
Full working libGDX example of the above:
package com.bornander.sandbox;
import com.badlogic.gdx.ApplicationAdapter;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input.Keys;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.badlogic.gdx.maps.tiled.TiledMap;
import com.badlogic.gdx.maps.tiled.TiledMapTile;
import com.badlogic.gdx.maps.tiled.TiledMapTileLayer;
import com.badlogic.gdx.maps.tiled.TiledMapTileLayer.Cell;
import com.badlogic.gdx.maps.tiled.TmxMapLoader;
import com.badlogic.gdx.maps.tiled.renderers.OrthogonalTiledMapRenderer;
import com.badlogic.gdx.math.Vector2;
public class SandboxGdxGame extends ApplicationAdapter {
private ShapeRenderer shapeRenderer;
private OrthographicCamera camera;
#Override
public void create () {
shapeRenderer = new ShapeRenderer();
camera = new OrthographicCamera(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
}
private static Color quantizedColor = new Color();
private static void rectQuantized(ShapeRenderer shapeRenderer, float x, float y, float width, float height, Color color1, Color color2, Color color3, Color color4, int steps) {
float dx = width / steps;
float dy = height / steps;
for(int sy = 0; sy < steps; ++sy) {
float py = (float)sy / (steps - 1);
for(int sx = 0; sx < steps; ++sx) {
float px = (float)sx / (steps - 1);
float minR = (1.0f - px) * color1.r + px * color2.r;
float maxR = (1.0f - px) * color4.r + px * color3.r;
float minG = (1.0f - px) * color1.g + px * color2.g;
float maxG = (1.0f - px) * color4.g + px * color3.g;
float minB = (1.0f - px) * color1.b + px * color2.b;
float maxB = (1.0f - px) * color4.b + px * color3.b;
float minA = (1.0f - px) * color1.a + px * color2.a;
float maxA = (1.0f - px) * color4.a + px * color3.a;
quantizedColor.set(minR + (maxR - minR) * py, minG + (maxG - minG) * py, minB + (maxB - minB) * py, minA + (maxA - minA) * py);
shapeRenderer.setColor(quantizedColor);
shapeRenderer.rect(x + dx * sx, y + dy * sy, dx, dy);
}
}
}
#Override
public void render () {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
camera.update();
shapeRenderer.setProjectionMatrix(camera.combined);
shapeRenderer.begin(ShapeType.Filled);
rectQuantized(shapeRenderer, 0, -100, 200, 200, Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW, 16);
shapeRenderer.rect(-200, -100, 200, 200, Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW);
shapeRenderer.end();
}
}

Related

cuda - directx 12 texture2D (in 1D array) interop

I'm trying to update in cuda a texture used in directx12. I may miss something but I have no tip about it.
there is an "all the time black" area in the top right area of the image.
only when I have R G B having the same value for all pixels, I get the expected result (modulo the first problem), if not I have unexpected artefacts, as if the array was not having the expected structure.
What do I miss ?
Here is the creation of the texture:
{
TextureWidth = m_width;
TextureHeight = m_height;
auto nPixels = TextureWidth * TextureHeight * 3;
auto pixelBufferSize = sizeof(float)* nPixels;
D3D12_RESOURCE_DESC textureDesc{};
textureDesc.MipLevels = 1;
textureDesc.Format = DXGI_FORMAT_R32G32B32_FLOAT;
textureDesc.Width = TextureWidth;
textureDesc.Height = TextureHeight;
textureDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
textureDesc.DepthOrArraySize = 1;
textureDesc.SampleDesc.Count = 1;
textureDesc.SampleDesc.Quality = 0;
textureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
ThrowIfFailed(m_device->CreateCommittedResource(&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT), D3D12_HEAP_FLAG_SHARED,
&textureDesc, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, nullptr, IID_PPV_ARGS(&m_textureBuffer)));
NAME_D3D12_OBJECT(m_textureBuffer);
// Describe and create a SRV for the texture.
{
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc{};
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.Format = textureDesc.Format;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MipLevels = 1;
m_device->CreateShaderResourceView(m_textureBuffer.Get(), &srvDesc, m_srvHeap->GetCPUDescriptorHandleForHeapStart());
NAME_D3D12_OBJECT(m_srvHeap);
}
// Share m_textureBuffer with cuda
{
HANDLE sharedHandle{};
WindowsSecurityAttributes windowsSecurityAttributes{};
LPCWSTR name{};
ThrowIfFailed(m_device->CreateSharedHandle(m_textureBuffer.Get(), &windowsSecurityAttributes, GENERIC_ALL, name, &sharedHandle));
D3D12_RESOURCE_ALLOCATION_INFO d3d12ResourceAllocationInfo;
d3d12ResourceAllocationInfo = m_device->GetResourceAllocationInfo(m_nodeMask, 1, &CD3DX12_RESOURCE_DESC::Buffer(pixelBufferSize));
auto actualSize = d3d12ResourceAllocationInfo.SizeInBytes;
cudaExternalMemoryHandleDesc externalMemoryHandleDesc;
memset(&externalMemoryHandleDesc, 0, sizeof(externalMemoryHandleDesc));
externalMemoryHandleDesc.type = cudaExternalMemoryHandleTypeD3D12Resource;
externalMemoryHandleDesc.handle.win32.handle = sharedHandle;
externalMemoryHandleDesc.size = actualSize;
externalMemoryHandleDesc.flags = cudaExternalMemoryDedicated;
checkCudaErrors(cudaImportExternalMemory(&m_externalMemory, &externalMemoryHandleDesc));
cudaExternalMemoryBufferDesc externalMemoryBufferDesc;
memset(&externalMemoryBufferDesc, 0, sizeof(externalMemoryBufferDesc));
externalMemoryBufferDesc.offset = 0;
externalMemoryBufferDesc.size = pixelBufferSize;
externalMemoryBufferDesc.flags = 0;
checkCudaErrors(cudaExternalMemoryGetMappedBuffer(&m_cudaDevVertptr, m_externalMemory, &externalMemoryBufferDesc));
RunKernel(TextureWidth, TextureHeight, (float*)m_cudaDevVertptr, m_streamToRun, 1.0f);
checkCudaErrors(cudaStreamSynchronize(m_streamToRun));
}
}
And here the cuda code for updating this texture:
int iDivUp(int a, int b) { return a % b != 0 ? a / b + 1 : a / b; }
__global__ void TextureKernel(float *pixels, unsigned int width, unsigned int height, float time)
{
unsigned int x = blockIdx.x*blockDim.x + threadIdx.x;
unsigned int y = blockIdx.y*blockDim.y + threadIdx.y;
if (y < height && x < width)
{
auto pos = (y * width + x) * 3;
auto sint = __sinf(time) * 0.1f + 0.10f;
auto sintAlt = (x / 32) % 2 == 0 ? 1.0f : sint;
pixels[pos + 0] = sintAlt; //RED
pixels[pos + 1] = 0; // (x + y) % 2 == 0 ? 1.0f : __sinf(time) * 0.25f + 0.75f; //GREEN
pixels[pos + 2] = 0; // (x + y) % 2 == 0 ? 1.0f : 0.0f; //BLUE
//pixels[pos + 0] = __sinf(time + 0.) * 0.5f + 0.5f;
//pixels[pos + 1] = __sinf(time * 0.09) * 0.5f + 0.5f;
//pixels[pos + 2] = __sinf(time + 2) * 0.5f + 0.5f;
}
}
void RunKernel(size_t meshWidth, size_t meshHeight, float *texture_dev, cudaStream_t streamToRun, float animTime)
{
//dim3 block(16, 16, 1);
//dim3 grid(meshWidth / 16, meshHeight / 16, 1);
auto unit = 32;
dim3 threads(unit, unit);
dim3 grid(iDivUp(meshWidth, unit), iDivUp(meshHeight, unit));
TextureKernel <<<grid, threads, 0, streamToRun >>>(texture_dev, meshWidth, meshHeight, animTime);
getLastCudaError("TextureKernel execution failed.\n");
}
And an extract of the resulting image I get with this code:
And the full repo if needed:
https://github.com/mprevot/CudaD3D12Update
EDIT
Two problems occur here.
The first is the format of texture: R32G32B32float, but the RTV (?) is expecting actually R32G32B32A32float. Matching everything at R32G32B32A32float can solve the weird colors arrays. The other way is to match the RTV to a R32G32B32float texture, but I don't see how.
The second problem is to work with cudaExternalMemoryGetMappedBuffer instead of cudaExternalMemoryGetMappedMipmappedArray; however how to use it with the texture described by D3D12_RESOURCE_DESC textureDesc{}; as well as a 1D cuda array float* is no clear yet.
I tried with the following code (for a 1D mipmap array), without success (cudaErrorInvalidValue).
auto textureSurface = TextureWidth * TextureHeight;
auto texturePixels = textureSurface * TextureChannels;
cudaExternalMemoryMipmappedArrayDesc cuTexDesc{};
cuTexDesc.numLevels = 1;
cuTexDesc.extent = make_cudaExtent(texturePixels, 0, 0);
cuTexDesc.formatDesc = cudaCreateChannelDesc<float>();
auto result = cudaMallocMipmappedArray(&cuMipArray[0], &cuTexDesc.formatDesc, cuTexDesc.extent, cuTexDesc.numLevels);
You assume that a 2D texture image with three channels of type float will have a simple row-wise linear memory layout. As demonstrated by your result, this is generally not true.
Textures are optimized for spatially-coherent access. Their memory layout is designed to keep things that are close in n-dimensional texture space close in memory. This cannot be achieved for anything with more than one dimension by a simple row-major memory layout. The exact memory layout of a particular texture image is generally not something you can assume to know or rely upon. It will depend on the GPU you're using (typically, the data will be stored in some way that employs things like tiling or Morton order, with padding in places to keep stuff aligned).
As you noticed yourself, what you want to do is use cudaExternalMemoryGetMappedMipmappedArray() to map a CUDA array (arrays are the CUDA-analogon to texture images) to your external data coming from D3D12. The format of this CUDA array will have to match the format of the texture created in D3D12. You should then be able to use the texture or surface functions of the CUDA runtime API to access the texture image represented by this CUDA array…
The right thing to do is to import the texture as external memory, then as mipmap array, then use this array to create a cuda surface, and then modify this surface in the cuda kernel.
The import and mapping is done this way:
cudaExternalMemoryMipmappedArrayDesc cuExtmemMipDesc{};
cuExtmemMipDesc.extent = make_cudaExtent(texDesc.Width, texDesc.Height, 0);
cuExtmemMipDesc.formatDesc = cudaCreateChannelDesc<float4>();
cuExtmemMipDesc.numLevels = 1;
cuExtmemMipDesc.flags = cudaArraySurfaceLoadStore;
cudaMipmappedArray_t cuMipArray{};
CheckCudaErrors(cudaExternalMemoryGetMappedMipmappedArray(&cuMipArray, m_externalMemory, &cuExtmemMipDesc));
cudaArray_t cuArray{};
CheckCudaErrors(cudaGetMipmappedArrayLevel(&cuArray, cuMipArray, 0));
cudaResourceDesc cuResDesc{};
cuResDesc.resType = cudaResourceTypeArray;
cuResDesc.res.array.array = cuArray;
checkCudaErrors(cudaCreateSurfaceObject(&cuSurface, &cuResDesc));
// where cudaSurfaceObject_t cuSurface{};
the cuda part looks like this:
int iDivUp(int a, int b) { return a % b != 0 ? a / b + 1 : a / b; }
__global__ void UpdateSurface(cudaSurfaceObject_t surf, unsigned int width, unsigned int height, float time)
{
unsigned int x = blockIdx.x * blockDim.x + threadIdx.x;
unsigned int y = blockIdx.y * blockDim.y + threadIdx.y;
if (y >= height | x >= width) return;
auto xVar = (float)x / (float)width;
auto yVar = (float)y / (float)height;
auto cost = __cosf(time) * 0.5f + 0.5f;
auto costx = __cosf(time) * 0.5f + xVar;
auto costy = __cosf(time) * 0.5f + yVar;
auto costxx = (__cosf(time) * 0.5f + 0.5f) * width;
auto costyy = (__cosf(time) * 0.5f + 0.5f) * height;
auto costxMany = __cosf(y * time) * 0.5f + yVar;
auto costyMany = __cosf((float)x/100 * time) * 0.5f + xVar;
auto margin = 1;
float4 pixel{};
if (y == 0) // paint the first row
pixel = make_float4(costyMany * 0.3, costyMany * 1, costyMany * 0.4, 1);
else if (y == height - 1) // paint the last row
pixel = make_float4(costyMany * 0.6, costyMany * 0.7, costyMany * 1, 1);
else if (x % 5 == 0) // paint a column of 1 pixel wide every 5 pixels
{
if (x > width / 2) // a certain color for the right half
pixel = make_float4(0.1, 0.5, costx * 1, 1);
else // another color for the left half
pixel = make_float4(costx * 1, 0.1, 0.2, 1);
}
else if (x > width - margin - 1 | x <= margin) // first and last columns
pixel = make_float4(costxMany, costxMany * 0.9, costxMany * 0.6, 1);
else // all the rest of the texture
pixel = make_float4(costx * 0.3, costx * 0.4, costx * 0.6, 1);
surf2Dwrite(pixel, surf, x * 16, y);
}
void RunKernel(size_t textureW, size_t textureH, cudaSurfaceObject_t surfaceObject, cudaStream_t streamToRun, float animTime)
{
auto unit = 10;
dim3 threads(unit, unit);
dim3 grid(iDivUp(textureW, unit), iDivUp(textureH, unit));
UpdateSurface <<<grid, threads, 0, streamToRun >>> (surfaceObject, textureW, textureH, animTime);
getLastCudaError("UpdateSurface execution failed.\n");
}
I updated the git repo to reflect those changes (https://github.com/mprevot/CudaD3D12Update)

World to screen coordinates in LibGDX

I am trying to present a map on the screen and place pins on the map at certain coordinates that are predefined. currently the issue is when the screen size changes say from device to device or when running on a desktop when the window size changes the position of all the pins moves when the map is scaled to fit the screen. I'm sure there is an obvious was to make the positions dependent on the map size or something. Could someone point me in the right direction in terms of making the objects appear in the same locations when the screen scales?
You need to unproject your coordinates with your camera.
Let's say that your coordinates that you want to transform to 'screen' coordinates are called
screenX and screenY.
Vector3 vec=new Vector3(screenX,screenY,0);
camera.unproject(vec);
Now you can use your vec.x and vec.y values.
This is how you do it correctly
public static Vector3 MultiplyPoint(Matrix4 m, Vector3 point)
{
Vector3 result = new Vector3(0, 0, 0);
result.x = m.val[Matrix4.M00] * point.x + m.val[Matrix4.M01] * point.y + m.val[Matrix4.M02]* point.z + m.val[Matrix4.M03];
result.y = m.val[Matrix4.M10] * point.x + m.val[Matrix4.M11] * point.y + m.val[Matrix4.M12] * point.z + m.val[Matrix4.M13];
result.z = m.val[Matrix4.M20] * point.x + m.val[Matrix4.M21] * point.y + m.val[Matrix4.M22] * point.z + m.val[Matrix4.M23];
float num = m.val[Matrix4.M30] * point.x + m.val[Matrix4.M31] * point.y + m.val[Matrix4.M32] * point.z + m.val[Matrix4.M33];
num = 1f / num;
result.x *= num;
result.y *= num;
result.z *= num;
return result;
}
public static Vector2 WorldToScreenPoint(float x, float y)
{
Matrix4 V = Constants.Camera.view;
Matrix4 P = Constants.Camera.projection;
Matrix4 MVP = P.mul(V); // Skipping M, point in world coordinates
Vector3 screenPos = MultiplyPoint(MVP, new Vector3(x, y, 0));
Vector3 screenPoint = new Vector3(screenPos.x + 1f, screenPos.y + 1f, screenPos.z + 1f).scl(0.5f); // returns x, y in [0, 1] internal.
return new Vector2(screenPoint.x * Constants.GlobalWidth, screenPoint.y * Constants.GlobalHeight); // multiply by viewport width and height to get the actual screen coordinates.
}

tex object access always returns zero -- any ideas?

I'm running CUDA 5.0, with compute_30,sm_30 set using a 670.
I create a mipmapped array via:
cudaExtent size;
size.width = window_width; // 600
size.height = window_height; // 600
size.depth = 1;
int levels = getMipMapLevels(size);
levels = MIN(levels, 9); // 9
cudaChannelFormatDesc fp32;
fp32.f = cudaChannelFormatKindFloat;
fp32.x = fp32.y = fp32.z = fp32.w = 32;
cudaMipmappedArray_t A;
checkCuda(cudaMallocMipmappedArray(&A, &fp32, size, levels, cudaArraySurfaceLoadStore));
I load the first level of A with surf2Dwrites. I know that works since I copy that array to the host and dump it to an image file. I now wish to fill the other miplevels of A with the mipmaps. One iteration through that loop looks like:
width >>= 1; width = MAX(1, width);
height >>= 1; height = MAX(1, height);
cudaArray_t from, to;
checkCuda(cudaGetMipmappedArrayLevel(&from, A, newlevel-1));
checkCuda(cudaGetMipmappedArrayLevel(&to, A, newlevel));
cudaTextureObject_t from_texture;
create_texture_object(from, true, &from_texture);
cudaSurfaceObject_t to_surface;
create_surface_object(to, &to_surface);
dim3 blocksize(16, 16, 1);
dim3 gridsize((width+blocksize.x-1)/blocksize.x,(height+blocksize.y-1)/blocksize.y, 1);
d_mipmap<<<gridsize, blocksize>>>(to_surface, from_texture, width, height);
checkCuda(cudaDeviceSynchronize());
checkCuda(cudaGetLastError());
uncreate_texture_object(&from_texture);
uncreate_surface_object(&to_surface);
The create_surface_object() code is known to work. Just in case, here's the create_texture_object() code:
static void create_texture_object(cudaArray_t tarray, bool filter_linear, cudaTextureObject_t *tobject)
{
assert(tarray && tobject);
// build the resource
cudaResourceDesc color_res;
memset(&color_res, 0, sizeof(cudaResourceDesc));
color_res.resType = cudaResourceTypeArray;
color_res.res.array.array = tarray;
// the texture descriptor
cudaTextureDesc texdesc;
memset(&texdesc, 0, sizeof(cudaTextureDesc));
texdesc.addressMode[0] = cudaAddressModeClamp;
texdesc.addressMode[1] = cudaAddressModeClamp;
texdesc.addressMode[2] = cudaAddressModeClamp;
texdesc.filterMode = filter_linear ? cudaFilterModeLinear : cudaFilterModePoint;
texdesc.normalizedCoords = 1;
checkCuda(cudaCreateTextureObject(tobject, &color_res, &texdesc, NULL));
}
The d_mipmap device function is the following:
__global__ void
d_mipmap(cudaSurfaceObject_t out, cudaTextureObject_t in, int w, int h)
{
float x = blockIdx.x * blockDim.x + threadIdx.x;
float y = blockIdx.y * blockDim.y + threadIdx.y;
float dx = 1.0/float(w);
float dy = 1.0/float(h);
if ((x < w) && (y < h))
{
#if 0
float4 color =
(tex2D<float4>(in, (x + .25f) * dx, (y + .25f) * dy)) +
(tex2D<float4>(in, (x + .75f) * dx, (y + .25f) * dy)) +
(tex2D<float4>(in, (x + .25f) * dx, (y + .75f) * dy)) +
(tex2D<float4>(in, (x + .75f) * dx, (y + .75f) * dy));
color /= 4.0f;
surf2Dwrite(color, mipOutput, x * sizeof(float4), y);
#endif
float4 color0 = tex2D<float4>(in, (x + .25f) * dx, (y + .25f) * dy);
surf2Dwrite(color0, out, x * sizeof(float4), y);
}
}
That contains both the mipmap sampling code (if'd out) plus debugging code.
The problem is, color0 is always uniformly zero, and I've been unable to understand why. I've changed the filtering to point (from linear) with no success. I've checked for errors. Nothing.
I am using CUDA/OpenGL interop here, but the mipmap generation is being done on CUDA arrays only.
I really really do not want to have to use texture references.
Any suggestions on where to look?
The bug turns out to be the use of cudaMipmappedArrays (either the array or the texture object -- I'm unable to tell which is broken.)
When I modify the code to use cudaArrays only, the texture reference starts working again.
Since the bindless texture program sample works, the bug appears to be limited to float32 channel mipmapped textures only. (I have a test program that shows the bug occurs with both 1 and 4 channel float32 mipmapped textures.)
I've reported the bug to Nvidia.

Algorithm for particles targeting

I'm building a particles systems, one of the features I'd like to add is a "target" feature. What I want to be able to do is set an X,Y target for each particle and make it go there, not in a straight line though (duh), but considering all other motion effects being applied on the particle.
The relevant parameters my particles have:
posx, posy : inits with arbitrary values. On each tick speedx and speedy are added to posx and posy respectively
speedx, speedy : inits with arbitrary values. On each tick accelx and accely are added to speedx speedy respectively if any)
accelx, accely : inits with arbitrary values. With current implementation stays constant through the lifespan of the particle.
life : starts with an arbitrary value, and 1 is reduced with each tick of the system.
What I want to achieve is the particle reaching the target X,Y on it's last life tick, while starting with it's original values (speeds and accelerations) so the motion towards the target will look "smooth". I was thinking of accelerating it in the direction of the target, while recalculating the needed acceleration force on each tick. That doesn't feel right though, would love to hear some suggestions.
For a "smooth" motion, you either keep the speed constant, or the acceleration constant, or the jerk constant. That depends on what you call "smooth" and what you call "boring". Let's keep the acceleration constant.
From a physics point of view, you have this constraint
targetx - posx = speedx*life + 1/2accelx * life * life
targety - posy = speedy*life + 1/2accely * life * life
Because distance traveled is v*t+1/2at^2. Solving for the unknown acceleration gives
accelx = (targetx - posx - speedx*life) / (1/2 * life * life)
accely = (targety - posy - speedy*life) / (1/2 * life * life)
(For this to work speedy must be in the same unit as time, for example "pixels per tick" and life is a number of "ticks". )
Since you use euler integration, this will not bring the particle exactly on the target. But I doubt it'll be a real issue.
Works like a charm:
Another picture, this time with constant jerk
jerkx = 6.0f*(targetx-x - speedx*life - 0.5f*accelx*life*life)/(life*life*life)
Looks like there is another bend in the curve...
Java code
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
#SuppressWarnings("serial")
public class TargetTest extends JPanel {
List<Particle> particles = new ArrayList<Particle>();
float tx, ty; // target position
public TargetTest() {
tx = 400;
ty = 400;
for (int i = 0; i < 50; i++)
particles.add(new Particle(tx / 2 + (float) (tx * Math.random()), ty / 2
+ (float) (ty * Math.random())));
this.setPreferredSize(new Dimension((int) tx * 2, (int) ty * 2));
}
#Override
protected void paintComponent(Graphics g1) {
Graphics2D g = (Graphics2D) g1;
g.setColor(Color.black);
// comment next line to draw curves
g.fillRect(0, 0, getSize().width, getSize().height);
for (Particle p : particles) {
p.update();
p.draw(g);
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
JFrame f = new JFrame("Particle tracking");
final TargetTest world = new TargetTest();
f.add(world);
// 1 tick every 50 msec
new Timer(50, new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
world.repaint();
}
}).start();
f.pack();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setVisible(true);
}
});
}
class Particle {
float x, y;// position
float vx, vy;// speed
float ax, ay;// acceleration
float jx, jy;// jerk
int life; // life
float lastx, lasty;// previous position, needed to draw lines
int maxlife; // maxlife, needed for color
public Particle(float x, float y) {
this.x = x;
this.y = y;
// pick a random direction to go to
double angle = 2 * Math.PI * Math.random();
setVelocity(angle, 2);// 2 pixels per tick = 2 pixels per 50 msec = 40
// pixels per second
// the acceleration direction 'should' be close to being perpendicular to
// the speed,
// makes it look interesting, try commenting it if you don't believe me ;)
if (Math.random() < 0.5)
angle -= Math.PI / 2;
else
angle += Math.PI / 2;
// add some randomness
angle += (Math.random() - 0.5) * Math.PI / 10;
setAcceleration(angle, 0.1);
life = (int) (100 + Math.random() * 100);
maxlife = life;
lastx = x;
lasty = y;
}
public void setVelocity(double angle, double speed) {
vx = (float) (Math.cos(angle) * speed);
vy = (float) (Math.sin(angle) * speed);
}
public void setAcceleration(double angle, double speed) {
ax = (float) (Math.cos(angle) * speed);
ay = (float) (Math.sin(angle) * speed);
}
#SuppressWarnings("unused")
private void calcAcceleration(float tx, float ty) {
ax = 2 * (tx - x - vx * life) / (life * life);
ay = 2 * (ty - y - vy * life) / (life * life);
}
private void calcJerk(float tx, float ty) {
jx = 6.0f * (tx - x - vx * life - 0.5f * ax * life * life)
/ (life * life * life);
jy = 6.0f * (ty - y - vy * life - 0.5f * ay * life * life)
/ (life * life * life);
}
public void update() {
lastx = x;
lasty = y;
if (--life <= 0)
return;
// calculate jerk
calcJerk(tx, ty);
// or uncomment and calculate the acceleration instead
// calcAcceleration(tx,ty);
ax += jx;
ay += jy;// increase acceleration
vx += ax;
vy += ay;// increase speed
x += vx;
y += vy;// increase position
}
public void draw(Graphics2D g) {
if (life < 0)
return;
g.setColor(new Color(255 - 255 * life / maxlife,
255 * life / maxlife,0));
g.drawLine((int) x, (int) y, (int) lastx, (int) lasty);
}
}
}
You could consider that your particule is initially "applied" a force (Fv) which corresponds to the inertia it has from its initial velocity. Then you apply an attraction force (Fa) that is proportionnal to the distance to the target. You can then sum those forces, and given a particle weight, you can deduce acceleration to consider at time t.
Fa(t) = (Constant / distanceToTarget(t))* [direction to target]
Fv(t) = [initialForce] * dampening(t)
a(t) = (Fa(t) + Fv(t)) / mass
Then you can compute v(t) from v(t-1) and a(t) as usual
Edit: I forgot the life of the particle can directly be computed from the distance to the target (for instance: life = distance / initialDistance will go from 1 at start and approch 0 near the target)
Edit: You could think of this as a kind of magnet. See wikipedia for the force formula.
one kind of movement you can use is the uniform acceleration http://en.wikipedia.org/wiki/Acceleration#Uniform_acceleration
Your particles will make a smoth move towards the target and hit it with rather high velocity
For meeting your stated criteria, do the following:
calculate the distance from the target, the particle will have at the end of it's life time, assuming the speed doesn't change from now on.
this distance put in this equation: http://upload.wikimedia.org/math/6/2/9/6295e1819e6bfe1101506caa4b4ec706.png and solve it for a
use this as your acceleration
Do this seperately for x and y

How to draw a rounded rectangle using HTML Canvas?

HTML Canvas provides methods for drawing rectangles, fillRect() and strokeRect(), but I can't find a method for making rectangles with rounded corners. How can I do that?
Nowadays you can just use context.roundRect. See further details on Kaiido's answer
var ctx = document.getElementById("rounded-rect").getContext("2d");
ctx.beginPath();
// Draw using 5px for border radius on all sides
// stroke it but no fill
ctx.roundRect(5, 5, 50, 50, 5);
ctx.stroke();
// To change the color on the rectangle, just manipulate the context
ctx.strokeStyle = "rgb(255, 0, 0)";
ctx.fillStyle = "rgba(255, 255, 0, .5)";
ctx.beginPath();
ctx.roundRect(100, 5, 100, 100, 20);
ctx.stroke();
ctx.fill();
// Manipulate it again
ctx.strokeStyle = "#0f0";
ctx.fillStyle = "#ddd";
// Different radii for each corner, top-left clockwise to bottom-left
ctx.beginPath();
ctx.roundRect(300, 5, 200, 100, [50,0,25,0]);
ctx.fill();
ctx.stroke();
<canvas id="rounded-rect" width="500" height="200">
<!-- Insert fallback content here -->
</canvas>
Old answer:
I needed to do the same thing and created a method to do it.
/**
* Draws a rounded rectangle using the current state of the canvas.
* If you omit the last three params, it will draw a rectangle
* outline with a 5 pixel border radius
* #param {CanvasRenderingContext2D} ctx
* #param {Number} x The top left x coordinate
* #param {Number} y The top left y coordinate
* #param {Number} width The width of the rectangle
* #param {Number} height The height of the rectangle
* #param {Number} [radius = 5] The corner radius; It can also be an object
* to specify different radii for corners
* #param {Number} [radius.tl = 0] Top left
* #param {Number} [radius.tr = 0] Top right
* #param {Number} [radius.br = 0] Bottom right
* #param {Number} [radius.bl = 0] Bottom left
* #param {Boolean} [fill = false] Whether to fill the rectangle.
* #param {Boolean} [stroke = true] Whether to stroke the rectangle.
*/
function roundRect(
ctx,
x,
y,
width,
height,
radius = 5,
fill = false,
stroke = true
) {
if (typeof radius === 'number') {
radius = {tl: radius, tr: radius, br: radius, bl: radius};
} else {
radius = {...{tl: 0, tr: 0, br: 0, bl: 0}, ...radius};
}
ctx.beginPath();
ctx.moveTo(x + radius.tl, y);
ctx.lineTo(x + width - radius.tr, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius.tr);
ctx.lineTo(x + width, y + height - radius.br);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius.br, y + height);
ctx.lineTo(x + radius.bl, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius.bl);
ctx.lineTo(x, y + radius.tl);
ctx.quadraticCurveTo(x, y, x + radius.tl, y);
ctx.closePath();
if (fill) {
ctx.fill();
}
if (stroke) {
ctx.stroke();
}
}
// Now you can just call
var ctx = document.getElementById("rounded-rect").getContext("2d");
// Draw using default border radius,
// stroke it but no fill (function's default values)
roundRect(ctx, 5, 5, 50, 50);
// To change the color on the rectangle, just manipulate the context
ctx.strokeStyle = "rgb(255, 0, 0)";
ctx.fillStyle = "rgba(255, 255, 0, .5)";
roundRect(ctx, 100, 5, 100, 100, 20, true);
// Manipulate it again
ctx.strokeStyle = "#0f0";
ctx.fillStyle = "#ddd";
// Different radii for each corner, others default to 0
roundRect(ctx, 300, 5, 200, 100, {
tl: 50,
br: 25
}, true);
<canvas id="rounded-rect" width="500" height="200">
<!-- Insert fallback content here -->
</canvas>
Different radii per corner provided by Corgalore
See http://js-bits.blogspot.com/2010/07/canvas-rounded-corner-rectangles.html
for further explanation
I started with #jhoff's solution, but rewrote it to use width/height parameters, and using arcTo makes it quite a bit more terse:
CanvasRenderingContext2D.prototype.roundRect = function (x, y, w, h, r) {
if (w < 2 * r) r = w / 2;
if (h < 2 * r) r = h / 2;
this.beginPath();
this.moveTo(x+r, y);
this.arcTo(x+w, y, x+w, y+h, r);
this.arcTo(x+w, y+h, x, y+h, r);
this.arcTo(x, y+h, x, y, r);
this.arcTo(x, y, x+w, y, r);
this.closePath();
return this;
}
Also returning the context so you can chain a little. E.g.:
ctx.roundRect(35, 10, 225, 110, 20).stroke(); //or .fill() for a filled rect
The HTML5 canvas doesn't provide a method to draw a rectangle with rounded corners.
How about using the lineTo() and arc() methods?
You can also use the quadraticCurveTo() method instead of the arc() method.
Juan, I made a slight improvement to your method to allow for changing each rectangle corner radius individually:
/**
* Draws a rounded rectangle using the current state of the canvas.
* If you omit the last three params, it will draw a rectangle
* outline with a 5 pixel border radius
* #param {Number} x The top left x coordinate
* #param {Number} y The top left y coordinate
* #param {Number} width The width of the rectangle
* #param {Number} height The height of the rectangle
* #param {Object} radius All corner radii. Defaults to 0,0,0,0;
* #param {Boolean} fill Whether to fill the rectangle. Defaults to false.
* #param {Boolean} stroke Whether to stroke the rectangle. Defaults to true.
*/
CanvasRenderingContext2D.prototype.roundRect = function (x, y, width, height, radius, fill, stroke) {
var cornerRadius = { upperLeft: 0, upperRight: 0, lowerLeft: 0, lowerRight: 0 };
if (typeof stroke == "undefined") {
stroke = true;
}
if (typeof radius === "object") {
for (var side in radius) {
cornerRadius[side] = radius[side];
}
}
this.beginPath();
this.moveTo(x + cornerRadius.upperLeft, y);
this.lineTo(x + width - cornerRadius.upperRight, y);
this.quadraticCurveTo(x + width, y, x + width, y + cornerRadius.upperRight);
this.lineTo(x + width, y + height - cornerRadius.lowerRight);
this.quadraticCurveTo(x + width, y + height, x + width - cornerRadius.lowerRight, y + height);
this.lineTo(x + cornerRadius.lowerLeft, y + height);
this.quadraticCurveTo(x, y + height, x, y + height - cornerRadius.lowerLeft);
this.lineTo(x, y + cornerRadius.upperLeft);
this.quadraticCurveTo(x, y, x + cornerRadius.upperLeft, y);
this.closePath();
if (stroke) {
this.stroke();
}
if (fill) {
this.fill();
}
}
Use it like this:
var canvas = document.getElementById("canvas");
var c = canvas.getContext("2d");
c.fillStyle = "blue";
c.roundRect(50, 100, 50, 100, {upperLeft:10,upperRight:10}, true, true);
Good news everyone!
roundRect(x, y, width, height, radii); is now officially part of the Canvas 2D API.
It is exposed on CanvasRenderingContext2D, Path2D and OffscreenCanvasRenderingContext2D objects.
Its radii parameter is an Array which contains either
a single float, representing the radius to use for all four corners,
two floats, for the top-left + bottom-right and top-right + bottom-left corners respectively,
three floats, for the top-left, top-right + bottom-left and bottom-right respectively,
or four floats, one per corner,
OR the same combinations, but with a DOMPointInit object, representing the x-radius and y-radius of each corner.
Currently, only Chrome has an implementation available, but you can find a polyfill I made, in this repo.
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
ctx.roundRect(20,20,80,80,[new DOMPoint(60,80), new DOMPoint(110,100)]);
ctx.strokeStyle = "green";
ctx.stroke();
const path = new Path2D();
path.roundRect(120,30,60,90,[0,25,new DOMPoint(60,80), new DOMPoint(110,100)]);
ctx.fillStyle = "purple";
ctx.fill(path);
// and a simple one
ctx.beginPath();
ctx.roundRect(200,20,80,80,[10]);
ctx.fillStyle = "orange";
ctx.fill();
<script src="https://cdn.jsdelivr.net/gh/Kaiido/roundRect#main/roundRect.js"></script>
<canvas></canvas>
This code creates a 100-pixel square, with rounded corners of 30 pixels.
var canvas = document.createElement("canvas");
document.body.appendChild(canvas);
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(100,100);
ctx.arcTo(0,100,0,0,30);
ctx.arcTo(0,0,100,0,30);
ctx.arcTo(100,0,100,100,30);
ctx.arcTo(100,100,0,100,30);
ctx.fill();
The drawPolygon function below can be used to draw any polygon with rounded corners.
See it running here.
function drawPolygon(ctx, pts, radius) {
if (radius > 0) {
pts = getRoundedPoints(pts, radius);
}
var i, pt, len = pts.length;
ctx.beginPath();
for (i = 0; i < len; i++) {
pt = pts[i];
if (i == 0) {
ctx.moveTo(pt[0], pt[1]);
} else {
ctx.lineTo(pt[0], pt[1]);
}
if (radius > 0) {
ctx.quadraticCurveTo(pt[2], pt[3], pt[4], pt[5]);
}
}
ctx.closePath();
}
function getRoundedPoints(pts, radius) {
var i1, i2, i3, p1, p2, p3, prevPt, nextPt,
len = pts.length,
res = new Array(len);
for (i2 = 0; i2 < len; i2++) {
i1 = i2-1;
i3 = i2+1;
if (i1 < 0) {
i1 = len - 1;
}
if (i3 == len) {
i3 = 0;
}
p1 = pts[i1];
p2 = pts[i2];
p3 = pts[i3];
prevPt = getRoundedPoint(p1[0], p1[1], p2[0], p2[1], radius, false);
nextPt = getRoundedPoint(p2[0], p2[1], p3[0], p3[1], radius, true);
res[i2] = [prevPt[0], prevPt[1], p2[0], p2[1], nextPt[0], nextPt[1]];
}
return res;
};
function getRoundedPoint(x1, y1, x2, y2, radius, first) {
var total = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)),
idx = first ? radius / total : (total - radius) / total;
return [x1 + (idx * (x2 - x1)), y1 + (idx * (y2 - y1))];
};
The function receives an array with the polygon points, like this:
var canvas = document.getElementById("cv");
var ctx = canvas.getContext("2d");
ctx.strokeStyle = "#000000";
ctx.lineWidth = 5;
drawPolygon(ctx, [[20, 20],
[120, 20],
[120, 120],
[ 20, 120]], 10);
ctx.stroke();
This is a port and a more generic version of a solution posted here.
Here's one I wrote... uses arcs instead of quadratic curves for better control over radius. Also, it leaves the stroking and filling up to you
/* Canvas 2d context - roundRect
*
* Accepts 5 parameters:
the start_x,
start_y points,
the end_x,
end_y points,
the radius of the corners
*
* No return value
*/
CanvasRenderingContext2D.prototype.roundRect = function(sx,sy,ex,ey,r) {
var r2d = Math.PI/180;
if( ( ex - sx ) - ( 2 * r ) < 0 ) { r = ( ( ex - sx ) / 2 ); } //ensure that the radius isn't too large for x
if( ( ey - sy ) - ( 2 * r ) < 0 ) { r = ( ( ey - sy ) / 2 ); } //ensure that the radius isn't too large for y
this.beginPath();
this.moveTo(sx+r,sy);
this.lineTo(ex-r,sy);
this.arc(ex-r,sy+r,r,r2d*270,r2d*360,false);
this.lineTo(ex,ey-r);
this.arc(ex-r,ey-r,r,r2d*0,r2d*90,false);
this.lineTo(sx+r,ey);
this.arc(sx+r,ey-r,r,r2d*90,r2d*180,false);
this.lineTo(sx,sy+r);
this.arc(sx+r,sy+r,r,r2d*180,r2d*270,false);
this.closePath();
}
Here is an example:
var _e = document.getElementById('#my_canvas');
var _cxt = _e.getContext("2d");
_cxt.roundRect(35,10,260,120,20);
_cxt.strokeStyle = "#000";
_cxt.stroke();
So this is based out of using lineJoin="round" and with the proper proportions, mathematics and logic I have been able to make this function, this is not perfect but hope it helps. If you want to make each corner have a different radius take a look at: https://p5js.org/reference/#/p5/rect
Here ya go:
CanvasRenderingContext2D.prototype.roundRect = function (x,y,width,height,radius) {
radius = Math.min(Math.max(width-1,1),Math.max(height-1,1),radius);
var rectX = x;
var rectY = y;
var rectWidth = width;
var rectHeight = height;
var cornerRadius = radius;
this.lineJoin = "round";
this.lineWidth = cornerRadius;
this.strokeRect(rectX+(cornerRadius/2), rectY+(cornerRadius/2), rectWidth-cornerRadius, rectHeight-cornerRadius);
this.fillRect(rectX+(cornerRadius/2), rectY+(cornerRadius/2), rectWidth-cornerRadius, rectHeight-cornerRadius);
this.stroke();
this.fill();
}
CanvasRenderingContext2D.prototype.roundRect = function (x,y,width,height,radius) {
radius = Math.min(Math.max(width-1,1),Math.max(height-1,1),radius);
var rectX = x;
var rectY = y;
var rectWidth = width;
var rectHeight = height;
var cornerRadius = radius;
this.lineJoin = "round";
this.lineWidth = cornerRadius;
this.strokeRect(rectX+(cornerRadius/2), rectY+(cornerRadius/2), rectWidth-cornerRadius, rectHeight-cornerRadius);
this.fillRect(rectX+(cornerRadius/2), rectY+(cornerRadius/2), rectWidth-cornerRadius, rectHeight-cornerRadius);
this.stroke();
this.fill();
}
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext('2d');
function yop() {
ctx.clearRect(0,0,1000,1000)
ctx.fillStyle = "#ff0000";
ctx.strokeStyle = "#ff0000"; ctx.roundRect(Number(document.getElementById("myRange1").value),Number(document.getElementById("myRange2").value),Number(document.getElementById("myRange3").value),Number(document.getElementById("myRange4").value),Number(document.getElementById("myRange5").value));
requestAnimationFrame(yop);
}
requestAnimationFrame(yop);
<input type="range" min="0" max="1000" value="10" class="slider" id="myRange1"><input type="range" min="0" max="1000" value="10" class="slider" id="myRange2"><input type="range" min="0" max="1000" value="200" class="slider" id="myRange3"><input type="range" min="0" max="1000" value="100" class="slider" id="myRange4"><input type="range" min="1" max="1000" value="50" class="slider" id="myRange5">
<canvas id="myCanvas" width="1000" height="1000">
</canvas>
Here's a solution using the lineJoin property to round the corners. It works if you just need a solid shape, but not so much if you need a thin border that's smaller than the border radius.
function roundedRect(ctx, options) {
ctx.strokeStyle = options.color;
ctx.fillStyle = options.color;
ctx.lineJoin = "round";
ctx.lineWidth = options.radius;
ctx.strokeRect(
options.x+(options.radius*.5),
options.y+(options.radius*.5),
options.width-options.radius,
options.height-options.radius
);
ctx.fillRect(
options.x+(options.radius*.5),
options.y+(options.radius*.5),
options.width-options.radius,
options.height-options.radius
);
ctx.stroke();
ctx.fill();
}
const canvas = document.getElementsByTagName("canvas")[0];
const ctx = canvas.getContext("2d");
roundedRect(ctx, {
x: 10,
y: 10,
width: 200,
height: 100,
radius: 35,
color: "red"
});
<canvas></canvas>
Opera, ffs.
if (window["CanvasRenderingContext2D"]) {
/** #expose */
CanvasRenderingContext2D.prototype.roundRect = function(x, y, w, h, r) {
if (w < 2*r) r = w/2;
if (h < 2*r) r = h/2;
this.beginPath();
if (r < 1) {
this.rect(x, y, w, h);
} else {
if (window["opera"]) {
this.moveTo(x+r, y);
this.arcTo(x+r, y, x, y+r, r);
this.lineTo(x, y+h-r);
this.arcTo(x, y+h-r, x+r, y+h, r);
this.lineTo(x+w-r, y+h);
this.arcTo(x+w-r, y+h, x+w, y+h-r, r);
this.lineTo(x+w, y+r);
this.arcTo(x+w, y+r, x+w-r, y, r);
} else {
this.moveTo(x+r, y);
this.arcTo(x+w, y, x+w, y+h, r);
this.arcTo(x+w, y+h, x, y+h, r);
this.arcTo(x, y+h, x, y, r);
this.arcTo(x, y, x+w, y, r);
}
}
this.closePath();
};
/** #expose */
CanvasRenderingContext2D.prototype.fillRoundRect = function(x, y, w, h, r) {
this.roundRect(x, y, w, h, r);
this.fill();
};
/** #expose */
CanvasRenderingContext2D.prototype.strokeRoundRect = function(x, y, w, h, r) {
this.roundRect(x, y, w, h, r);
this.stroke();
};
}
Since Opera is going WebKit, this should also remain valid in the legacy case.
To make the function more consistent with the normal means of using a canvas context, the canvas context class can be extended to include a 'fillRoundedRect' method -- that can be called in the same way fillRect is called:
var canv = document.createElement("canvas");
var cctx = canv.getContext("2d");
// If thie canvasContext class doesn't have a fillRoundedRect, extend it now
if (!cctx.constructor.prototype.fillRoundedRect) {
// Extend the canvaseContext class with a fillRoundedRect method
cctx.constructor.prototype.fillRoundedRect =
function (xx,yy, ww,hh, rad, fill, stroke) {
if (typeof(rad) == "undefined") rad = 5;
this.beginPath();
this.moveTo(xx+rad, yy);
this.arcTo(xx+ww, yy, xx+ww, yy+hh, rad);
this.arcTo(xx+ww, yy+hh, xx, yy+hh, rad);
this.arcTo(xx, yy+hh, xx, yy, rad);
this.arcTo(xx, yy, xx+ww, yy, rad);
if (stroke) this.stroke(); // Default to no stroke
if (fill || typeof(fill)=="undefined") this.fill(); // Default to fill
}; // end of fillRoundedRect method
}
The code checks to see if the prototype for the constructor for the canvas context object contains a 'fillRoundedRect' property and adds one -- the first time around. It is invoked in the same manner as the fillRect method:
ctx.fillStyle = "#eef"; ctx.strokeStyle = "#ddf";
// ctx.fillRect(10,10, 200,100);
ctx.fillRoundedRect(10,10, 200,100, 5);
The method uses the arcTo method as Grumdring did. In the method, this is a reference to the ctx object. The stroke argument defaults to false if undefined. The fill argument defaults to fill the rectangle if undefined.
(Tested on Firefox, I don't know if all implementations permit extension in this manner.)
Method 1: Using path-drawing methods
The most straightforward method of doing this with HTML Canvas is by using the path-drawing methods of ctx:
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
function roundedRect(ctx, x, y, width, height, radius) {
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
ctx.lineTo(x + width, y + height - radius);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
ctx.lineTo(x + radius, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
ctx.closePath();
}
ctx.fillStyle = "red";
roundedRect(ctx, 10, 10, 100, 100, 20);
ctx.fill();
<canvas id="canvas">
<!-- Fallback content -->
</canvas>
Method 2: Using Path2D
You can also draw rounded rectangles in HTML Canvas by using the Path2D interface:
Example 1
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
function roundedRect(x, y, width, height, radius) {
return new Path2D(`M ${x + radius} ${y} H ${x + width - radius} a ${radius} ${radius} 0 0 1 ${radius} ${radius} V ${y + height - radius} a ${radius} ${radius} 0 0 1 ${-radius} ${radius} H ${x + radius} a ${radius} ${radius} 0 0 1 ${-radius} ${-radius} V ${y + radius} a ${radius} ${radius} 0 0 1 ${radius} ${-radius}`);
}
ctx.fillStyle = "blue";
ctx.fill(roundedRect(10, 10, 100, 100, 20));
<canvas id="canvas">
<!-- Fallback content -->
</canvas>
Example 2
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
function roundedRect(x, y, width, height, radius) {
let path = new Path2D();
path.moveTo(x + radius, y);
path.lineTo(x + width - radius, y);
path.quadraticCurveTo(x + width, y, x + width, y + radius);
path.lineTo(x + width, y + height - radius);
path.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
path.lineTo(x + radius, y + height);
path.quadraticCurveTo(x, y + height, x, y + height - radius);
path.lineTo(x, y + radius);
path.quadraticCurveTo(x, y, x + radius, y);
path.closePath();
return path;
}
ctx.fillStyle = "green";
ctx.fill(roundedRect(10, 10, 100, 100, 20));
<canvas id="canvas">
<!-- Fallback content -->
</canvas>
try to add this line , when you want to get rounded corners : ctx.lineCap = "round";
NONE of the other answers can handle the following 3 cases correctly:
if ((width >= radius x 2) && (height <= radius * 2))
if ((width <= radius x 2) && (height >= radius * 2))
if ((width <= radius x 2) && (height <= radius * 2))
If any of these cases happen, you will not get a correctly drawn rectangle
My Solution handles ANY radius and ANY Width and Height dynamically, and should be the default answer
function roundRect(ctx, x, y, width, height, radius) {
/*
* Draws a rounded rectangle using the current state of the canvas.
*/
let w = width;
let h = height;
let r = radius;
ctx.stroke()
ctx.fill()
ctx.beginPath();
// Configure the roundedness of the rectangles corners
if ((w >= r * 2) && (h >= r * 2)) {
// Handles width and height larger than diameter
// Keep radius fixed
ctx.moveTo(x + r, y); // tr start
ctx.lineTo(x + w - r, y); // tr
ctx.quadraticCurveTo(x + w, y, x + w, y + r); //tr
ctx.lineTo(x + w, y + h - r); // br
ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h); // br
ctx.lineTo(x + r, y + h); // bl
ctx.quadraticCurveTo(x, y + h, x, y + h - r); // bl
ctx.lineTo(x, y + r); // tl
ctx.quadraticCurveTo(x, y, x + r, y); // tl
} else if ((w < r * 2) && (h > r * 2)) {
// Handles width lower than diameter
// Radius must dynamically change as half of width
r = w / 2;
ctx.moveTo(x + w, y + h - r); // br start
ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h); // br curve
ctx.quadraticCurveTo(x, y + h, x, y + h - r) // bl curve
ctx.lineTo(x, y + r); // line
ctx.quadraticCurveTo(x, y, x + r, y); // tl
ctx.quadraticCurveTo(x + w, y, x + w, y + r); // tl
ctx.lineTo(x + w, y + h - r); // line
} else if ((w > r * 2) && (h < r * 2)) {
// Handles height lower than diameter
// Radius must dynamically change as half of height
r = h / 2;
ctx.moveTo(x + w - r, y + h); // br start
ctx.quadraticCurveTo(x + w, y + h, x + w, y + r); // br curve
ctx.quadraticCurveTo(x + w, y, x + w - r, y); // tr curve
ctx.lineTo(x + r, y); // line between tr tl
ctx.quadraticCurveTo(x, y, x, y + r); // tl curve
ctx.quadraticCurveTo(x, y + h, x + r, y + h); // bl curve
} else if ((w < 2 * r) && (h < 2 * r)) {
// Handles width and height lower than diameter
ctx.moveTo(x + w / 2, y + h);
ctx.quadraticCurveTo(x + w, y + h, x + w, y + h / 2); // bl curve
ctx.quadraticCurveTo(x + w, y, x + w / 2, y); // tr curve
ctx.quadraticCurveTo(x, y, x, y + h / 2); // tl curve
ctx.quadraticCurveTo(x, y + h, x + w / 2, y + h); // bl curve
}
ctx.closePath();
}