Closed ArEnSc closed 1 month ago
Make a small demo. I don't understand what you are trying to do.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, user-scalable=no"
/>
<title>Konva App</title>
<script src="https://cdn.jsdelivr.net/npm/konva@8.3.5/konva.min.js"></script>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
width: 100vw;
height: 100vh;
}
#container {
width: 100%;
height: 100%;
}
#captureButton {
position: absolute;
top: 10px;
left: 10px;
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
#captureButton:hover {
background-color: #0056b3;
}
</style>
</head>
<body>
<div id="container"></div>
<button id="captureButton">Capture Canvas</button>
<script>
// mobile screen resolution
// create off screen canvas to draw to
const width = 720
const height = 1280
const offScreenCanvas = new OffscreenCanvas(width, height)
const context = offScreenCanvas.getContext("2d")
// Create a Konva Stage
const stage = new Konva.Stage({
container: "container", // ID of the container element
width: window.innerWidth,
height: window.innerHeight,
})
// Create a Konva Layer
const layer = new Konva.Layer()
stage.add(layer)
// Create a rectangle with dimensions 720 by 1280
const rect = new Konva.Rect({
x: (window.innerWidth - width) / 2, // Center the rectangle horizontally
y: (window.innerHeight - height) / 2, // Center the rectangle vertically
width: width,
height: height,
fill: "red", // Fill color
stroke: "green", // Border color
strokeWidth: 5, // Border width
})
// Add the rectangle to the layer
layer.add(rect)
// Function to create a star
function createStar(x, y) {
return new Konva.Star({
x: x,
y: y,
numPoints: 5,
innerRadius: 20,
outerRadius: 40,
fill: "yellow",
stroke: "black",
strokeWidth: 2,
})
}
// Calculate positions for the stars
const rectX = rect.x()
const rectY = rect.y()
const rectWidth = rect.width()
const rectHeight = rect.height()
const positions = [
{ x: rectX, y: rectY }, // Top-left corner
{ x: rectX + rectWidth, y: rectY }, // Top-right corner
{ x: rectX, y: rectY + rectHeight }, // Bottom-left corner
{ x: rectX + rectWidth, y: rectY + rectHeight }, // Bottom-right corner
{ x: rectX + rectWidth / 2, y: rectY + rectHeight / 2 }, // Center
]
// Add stars to the specified positions
positions.forEach((pos) => {
const star = createStar(pos.x, pos.y)
layer.add(star)
})
// Draw the layer
layer.draw()
// Define an async function to capture the canvas content
async function captureCanvas() {
// Capture with the offscreen canvas
context.drawImage(
layer.getCanvas()._canvas,
rectX,
rectY,
rectWidth,
rectHeight,
0,
0,
width,
height
)
// Convert the offscreen canvas to a Blob
const blob = await offScreenCanvas.convertToBlob({
quality: 1.0,
type: "image/jpeg",
})
// I want to call this and send this buffer to a shared worker
// for faster processing.
//offScreenCanvas.transferToImageBitmap();
// Create a URL for the Blob and display the image
const url = URL.createObjectURL(blob)
const img = new Image()
img.src = url
document.body.appendChild(img) // Append the image to the body or any other container
// Optionally, you can download the captured image
const link = document.createElement("a")
link.href = url
link.download = "snapshot.jpg"
link.click()
}
// Attach event listener to the button
document
.getElementById("captureButton")
.addEventListener("click", captureCanvas)
</script>
</body>
</html>
This is a toy example: But I want to be able to capture any Konva items that sit on top of that red layer. It seems to work but when you zoom in and out. It causes issues with things being offset. With it full screen it is a bit offset still. I want to do it this way because I want to be a able to convert the offscreen blob on a shared worker.
I have taken a look at using dataToURL on the layer, and it captures everything on top of the layer. However its way to slow for my purposes.
So do you know how I would be able to capture this layer and anything that would sit on top of it correctly?
Here's another toy example that also tries to account for resizing of the window it you zoom in and out of the browser window with control + or -. or minimize the window the capture will be incorrect.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, user-scalable=no"
/>
<title>Konva App</title>
<script src="https://cdn.jsdelivr.net/npm/konva@8.3.5/konva.min.js"></script>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
width: 100vw;
height: 100vh;
}
#container {
width: 100%;
height: 100%;
}
#captureButton {
position: absolute;
top: 10px;
left: 10px;
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
#captureButton:hover {
background-color: #0056b3;
}
</style>
</head>
<body>
<div id="container"></div>
<button id="captureButton">Capture Canvas</button>
<script>
// mobile screen resolution
// create off screen canvas to draw to
const width = 720
const height = 1280
const offScreenCanvas = new OffscreenCanvas(width, height)
const context = offScreenCanvas.getContext("2d")
// Create a Konva Stage
const stage = new Konva.Stage({
container: "container", // ID of the container element
width: window.innerWidth,
height: window.innerHeight,
})
// Create a Konva Layer
const layer = new Konva.Layer()
stage.add(layer)
// Create a rectangle with dimensions 720 by 1280
const rect = new Konva.Rect({
x: (window.innerWidth - width) / 2, // Center the rectangle horizontally
y: (window.innerHeight - height) / 2, // Center the rectangle vertically
width: width,
height: height,
fill: "red", // Fill color
stroke: "green", // Border color
strokeWidth: 5, // Border width
})
// Add the rectangle to the layer
layer.add(rect)
// Function to create a star
function createStar(x, y) {
return new Konva.Star({
x: x,
y: y,
numPoints: 5,
innerRadius: 20,
outerRadius: 40,
fill: "yellow",
stroke: "black",
strokeWidth: 2,
})
}
// Function to calculate positions for the stars
function calculateStarPositions(rect) {
const rectX = rect.x()
const rectY = rect.y()
const rectWidth = rect.width()
const rectHeight = rect.height()
return [
{ x: rectX, y: rectY }, // Top-left corner
{ x: rectX + rectWidth, y: rectY }, // Top-right corner
{ x: rectX, y: rectY + rectHeight }, // Bottom-left corner
{ x: rectX + rectWidth, y: rectY + rectHeight }, // Bottom-right corner
{ x: rectX + rectWidth / 2, y: rectY + rectHeight / 2 }, // Center
]
}
// Create and add stars to the layer
let stars = []
function addStars() {
const positions = calculateStarPositions(rect)
positions.forEach((pos) => {
const star = createStar(pos.x, pos.y)
stars.push(star)
layer.add(star)
})
}
addStars()
// Draw the layer
layer.draw()
// Function to reposition the rectangle and stars
function repositionElements() {
rect.x((window.innerWidth - width) / 2)
rect.y((window.innerHeight - height) / 2)
const positions = calculateStarPositions(rect)
stars.forEach((star, index) => {
star.x(positions[index].x)
star.y(positions[index].y)
})
stage.width(window.innerWidth)
stage.height(window.innerHeight)
layer.draw()
}
// Add event listener for window resize
window.addEventListener("resize", repositionElements)
// Define an async function to capture the canvas content
async function captureCanvas() {
// Capture with the offscreen canvas
offScreenCanvas.width = width
offScreenCanvas.height = height
context.drawImage(
layer.getCanvas()._canvas,
rect.x(),
rect.y(),
rect.width(),
rect.height(),
0,
0,
width,
height
)
// Convert the offscreen canvas to a Blob
const blob = await offScreenCanvas.convertToBlob({
quality: 1.0,
type: "image/jpeg",
})
// Create a URL for the Blob and display the image
const url = URL.createObjectURL(blob)
const img = new Image()
img.src = url
document.body.appendChild(img) // Append the image to the body or any other container
// Optionally, you can download the captured image
const link = document.createElement("a")
link.href = url
link.download = "snapshot.jpg"
link.click()
}
// Attach event listener to the button
document
.getElementById("captureButton")
.addEventListener("click", captureCanvas)
</script>
</body>
</html>
@lavrton thanks for responding!
I still don't get it.
Why don't you use regular toDataURL() or toCanvas() exports? If it is slow for you, can you give a context when/how you call it?
You may have scale issues, because of pixelRatio. Canvas size of the layer may be bigger than css size, to have sharp images on HDPI devices like retina.
to
Yeah it was the scale issues, I dug into the code and created something that would allow me do what u wanted by subclassing then draw scene
I was using this where # captureCanvas: Konva.Rect;
const dataURL = this.videoLayer.toDataURL({ x: this.captureCanvas.x(), y: this.captureCanvas.y(), width: this.captureCanvas.width(), height: this.captureCanvas.height(), });
to capture what was in the a konva ImageRect (this.captureCanvas) It works however is super slow.
I need to get direct access to the data in that region in the layer. But using the offScreenCanvas and grabbing the 2D context.
So I was able to set that up, however... when performing this operation
It doesn't seem grab the region correctly either due to scaling issues? when I zoom in and out of the browser a bit, it seems to work but then on different displays it doesn't capture it correctly. I suspect this has to do with pixel ratio? I need it to be rendered on the offScreen Canvas so I can send this off to a web worker to process @lavrton lmk if if you have any other ideas of how to achieve this.