Hello i am working on online print application where we create some designs in FabricJS and then users can edit those designing replacing text and image with their own...everything is working perfect but we are facing an issue with image replace when users replace image we want it to fit in already present image on canvas also maintain aspect ratio..below is current code for replacing image but its does not not fit image if image is vertical.
o = activeCanvas.getActiveObject();
fabric.Image.fromURL(src, function(m) {
var i = m.set({
id: o.id,
left: o.left,
top: o.top,
width: m.width,
height: m.height
});
i.scaleToWidth(o.getScaledWidth());
activeCanvas.remove(o);
activeCanvas.add(i);
renderAppChange();
zoomoutIn(1);
setActiveNew(i);
});
From what I see, you want to scale your new image to fit the smallest of width and height dimensions of the old image:
const widthFactor = old.getScaledWidth() / newImg.width
const heightFactor = old.getScaledHeight() / newImg.height
const minFactor = Math.min(widthFactor, heightFactor)
newImg.scale(minFactor)
Here, scale() will preserve the aspect ratio.
And a snippet to test:
const canvas = new fabric.Canvas("c")
const url = "https://via.placeholder.com/100x50"
const url2 = "https://via.placeholder.com/50x100"
const url3 = "https://via.placeholder.com/100x100"
fabric.Image.fromURL(url, (img) => {
canvas.add(img)
}, {
left: 0,
top: 10,
})
fabric.Image.fromURL(url2, (img) => {
canvas.add(img)
}, {
left: 120,
top: 10,
})
fabric.Image.fromURL(url3, (img) => {
canvas.add(img)
}, {
left: 200,
top: 10,
})
function insertAndFit (imageURL) {
const o = canvas.getActiveObject();
if (!o) {
throw new Error('select an object first')
}
fabric.Image.fromURL(imageURL, function(m) {
m.set({
left: o.left,
top: o.top
})
const widthFactor = o.getScaledWidth() / m.width
const heightFactor = o.getScaledHeight() / m.height
const minFactor = Math.min(widthFactor, heightFactor)
m.scale(minFactor)
canvas.remove(o)
canvas.add(m)
})
}
document.getElementById('b1').onclick = () => {
insertAndFit("https://via.placeholder.com/100x300")
}
document.getElementById('b2').onclick = () => {
insertAndFit("https://via.placeholder.com/300x100")
}
document.getElementById('b3').onclick = () => {
insertAndFit("https://via.placeholder.com/200x200")
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.3.2/fabric.js"></script>
<canvas id='c' width="400" height="200"></canvas>
<button id='b1'>insert 100x300</button>
<button id='b2'>insert 300x100</button>
<button id='b3'>insert 200x200</button>
Related
I am using PIXIjs to draw and modify large size Image! for large size image the image get cropped as following:
but on firefox is totally okay:
I saw something as following on DEVtool-chrome that I think it might be reason for this and tried to override this but does not work:
I would like to have the full image as firefox on chrome as well!
here is js:
private drawImage(): void {
PIXI.Loader.shared.reset();
const frame = document.querySelector('#frame');
const url = this.urlInputImage;
this.removeAllChildNodes(frame);
const app = new PIXI.Application({
backgroundColor: 0xffffff,
});
const container = new PIXI.Container();
app.stage.interactive = true;
app.stage.on('rightclick', e =>{
this.contextmenuClick.emit(e);
this.rightClick(e);
});
const graphics = new PIXI.Graphics();
this.detectedPositions.forEach((detectedInfo) => {
const boundingBox = detectedInfo['boundingBox'];
graphics.lineStyle(/*border width*/5,/*border color*/ PIXI.utils.string2hex(detectedInfo['detectionBoxesHexColor']), 1);
const points = Object.keys(boundingBox).map(function(boundingBoxIndex){
return boundingBox[boundingBoxIndex];
});
const pointsFiltered = points.filter((point) => {
return point !== null;
});
graphics.drawPolygon(pointsFiltered);
graphics.endFill();
});
PIXI.Loader.shared
.add('layout',url)
.load(setup);
function setup() {
const pic = new PIXI.Sprite(PIXI.Loader.shared.resources.layout.texture);
const border = new PIXI.Graphics();
const borderWidth = 5;
const borderColor = 1261722;
border.lineStyle(borderWidth, borderColor);
border.drawRect(app.stage.x , app.stage.y , pic.width , pic.height );
container.addChild(pic);
container.addChild(border);
container.addChild(graphics);
app.stage.addChild(container);
const w = pic.width;
const h = pic.height;
app.renderer.view.style.width = w + "px";
app.renderer.view.style.height = h + "px";
//this part adjusts the ratio:
app.renderer.resize(w, h);
}
frame.appendChild(app.view);
PIXI.BaseTexture.removeFromCache('layout');
}
here is the css:
:host {
display: block;
}
canvas {
background-color: white;
}
#frame {
overflow: auto;
background-color: white;
height: 450px;
width: auto;
}`
here is html
<div id="frame"></div>
I am using react-image-marker which overlays marker on image inside a div.
<ImageMarker
src={props.asset.url}
markers={markers}
onAddMarker={(marker) => setMarkers([...markers, marker])}
className="object-fit-contain image-marker__image"
/>
The DOM elements are as follows:
<div class=“image-marker”>
<img src=“src” class=“image-marker__image” />
</div>
To make vertically long images fit the screen i have added css to contain the image within div
.image-marker {
width: inherit;
height: inherit;
}
.image-marker__image {
object-fit:contain !important;
width: inherit;
height: inherit;
}
But now the image is only a subpart of the entire marker area. Due to which marker can be added beyond image bounds, which i do not want.
How do you think i can tackle this. After the image has been loaded, how can i change the width of parent div to make sure they have same size and markers remain in the image bounds. Please specify with code if possible
Solved it by adding event listeners in useLayoutEffect(). Calculating image size info after it is rendered and adjusting the parent div dimensions accordingly. Initially and also when resize occurs.
If you think you have a better solution. Do specify.
useLayoutEffect(() => {
const getRenderedSize = (contains, cWidth, cHeight, width, height, pos) => {
var oRatio = width / height,
cRatio = cWidth / cHeight;
return function () {
if (contains ? (oRatio > cRatio) : (oRatio < cRatio)) {
this.width = cWidth;
this.height = cWidth / oRatio;
} else {
this.width = cHeight * oRatio;
this.height = cHeight;
}
this.left = (cWidth - this.width) * (pos / 100);
this.right = this.width + this.left;
return this;
}.call({});
}
const getImgSizeInfo = (img) => {
var pos = window.getComputedStyle(img).getPropertyValue('object-position').split(' ');
return getRenderedSize(true,
img.width,
img.height,
img.naturalWidth,
img.naturalHeight,
parseInt(pos[0]));
}
const imgDivs = reactDom.findDOMNode(imageCompRef.current).getElementsByClassName("image-marker__image")
if (imgDivs) {
if (imgDivs.length) {
const thisImg = imgDivs[0]
thisImg.addEventListener("load", (evt) => {
if (evt) {
if (evt.target) {
if (evt.target.naturalWidth && evt.target.naturalHeight) {
let renderedImgSizeInfo = getImgSizeInfo(evt.target)
if (renderedImgSizeInfo) {
setOverrideWidth(Math.round(renderedImgSizeInfo.width))
}
}
}
}
})
}
}
function updateSize() {
const thisImg = imgDivs[0]
if (thisImg){
let renderedImgSizeInfo = getImgSizeInfo(thisImg)
if (renderedImgSizeInfo) {
setOverrideWidth((prev) => {
return null
})
setTimeout(() => {
setOverrideWidth((prev) => {
return setOverrideWidth(Math.round(renderedImgSizeInfo.width))
})
}, 390);
}
}
}
window.addEventListener('resize', updateSize);
return () => window.removeEventListener('resize', updateSize);
}, [])
I have a unique (but hopefully simple) issue to fix with Fabric.js to fix.
I have this very simple example below:
I have
2 Images
Both have an absolutePositioned mask via clipPath property on the fabric.Image instance
My issue is:
I want the image to only be selectable (and hoverable) whenever the selection happens within the bounds of the mask, not anywhere on image (even outside of the bounds of its mask).
This image shows the mouse hovering over the red door picture (even though the mouse is outside of the mask bounds, but not outside the image bounds:
Here's a code snippet of the door image snippet:
fabric.Image.fromURL(url1, function(img){
canvas.add(img.set({
left: 0,
top: 0,
clipPath: rect1,
hasControls: false,
}));
img.on('mouseover', () => {
const filter = new fabric.Image.filters.BlendColor({
color: 'white',
alpha: 0.7,
mode: 'tint'
})
img.filters.push(filter)
img.applyFilters()
canvas.renderAll()
})
img.on('mouseout', () => {
img.filters.pop()
img.applyFilters()
canvas.renderAll()
})
}, {crossOrigin: "Anonymous"});
JS Fiddle Example showing the current behavior that I'm trying to change.
A possible solution is to listen to the mouse event in the canvas and toggle the activeObject based on the mouse position:
canvas.observe('mouse:move', function(options) {
const pos = canvas.getPointer(options.e);
if (!imageRight || !imageLeft) return
if (pos.x > 200) {
activeImage = imageRight
} else {
activeImage = imageLeft
}
const activeObj = canvas.getActiveObject();
if (activeImage !== activeObj) {
canvas.setActiveObject(activeImage);
canvas.renderAll()
}
});
This is a very simplified way to detect if the mouse is over the image. It checks if the x > 200 since that is where the line between both images is positioned. This could be improved to be more accurate, but it works just to illustrate the idea.
var canvas = window._canvas = new fabric.Canvas('c');
const url1 = 'https://picsum.photos/300/200'
const url2 = 'https://picsum.photos/320'
let imageRight
let imageLeft
let activeImage
canvas.observe('mouse:move', function(options) {
const pos = canvas.getPointer(options.e);
if (!imageRight || !imageLeft) return
if (pos.x > 200) {
activeImage = imageRight
} else {
activeImage = imageLeft
}
const activeObj = canvas.getActiveObject();
if (activeImage !== activeObj) {
canvas.setActiveObject(activeImage);
canvas.renderAll()
}
});
const baseProps = {
width: 200,
height: 200,
top: 0,
fill: '#000',
absolutePositioned: true,
hasControls: false,
evented: false,
selectable: false
}
const rect1 = new fabric.Rect({
...baseProps,
left: 0,
})
const rect2 = new fabric.Rect({
...baseProps,
left: 200,
})
canvas.add(rect1)
canvas.add(rect2)
fabric.Image.fromURL(url2, function(img) {
imageRight = img
canvas.add(img.set({
left: 200,
top: 0,
clipPath: rect2,
hasControls: false
}))
}, {
crossOrigin: "Anonymouse"
})
fabric.Image.fromURL(url1, function(img) {
imageLeft = img
canvas.add(img.set({
left: 0,
top: 0,
clipPath: rect1,
hasControls: false,
}));
}, {
crossOrigin: "Anonymous"
});
canvas {
border: 2px red solid;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.6.0/fabric.min.js"></script>
<canvas id="c" width="420" height="220"></canvas>
Consider a image file .png
Consider you have the xy-cords of some element like x:400, y:500
Consider you have the size of image to be cropped: width: 50, height: 20
I have below snippet from nodejs pack easyimage and I have installed ImageMagick too.
When I run below code, it just passes but unable to crop image.
easyimage.crop(
{
src: "F:/screenshot.png", // Contains fullscreen image
dst: "F:/screenshot.png", // New image with cropped name
x: 400,
y: 500,
cropwidth: 50,
cropheight: 20,
gravity: "North-West",
},
(err, stdout, stderr) => {
if (err) throw err
}
)
I use sharp for this and it works pretty well
Try this
const sharp = require('sharp')
sharp('./kangta.jpg')
.extract({ left: 0, top: 0, width: 100, height: 100 })
.toFile('./kangta.new.jpg', function (err) {
if (err) console.log(err);
})
sharp: https://www.npmjs.com/package/sharp
Here I extend the example given above with a Puppeteer screenshot that gets split/cropped into multiple images with Sharp:
const puppeteer = require('puppeteer');
const sharp = require('sharp');
// original image
let originalImage = 'originalImage.jpg';
// file name for cropped image
let outputImage = 'croppedImage.jpg';
let outputConcat = '';
async function run(){
const browser = await puppeteer.launch({headless:true})
const page = await browser.newPage();
await page.setViewport({
width: 1920,
height: 1080,
deviceScaleFactor: 1,
});
await page.goto('https://en.wikipedia.org/wiki/Main_Page');
await page.screenshot({path: 'originalImage.jpg', fullPage:true});
await browser.close();
var sizeOf = require('image-size');
var dimensions = sizeOf('originalImage.jpg');
console.log(dimensions.width, dimensions.height);
const printText = (newHeight, newTop, imageIndex) => {
console.log('newHeight: ' + newHeight + ', newTop: ' + newTop + ', imageIndex: ' + imageIndex);
};
const cropImage = (newHeight, newTop, imageIndex) => {
sharp('originalImage.jpg')
.extract({left: 0, width: dimensions.width, height: newHeight, top: newTop})
.toFile(outputConcat.concat(imageIndex, outputImage))
};
var remainingTop = dimensions.height;
var cumulitiveTop = 0;
var amountOfImages = Math.ceil(dimensions.height / 1080);
for (let i = 0; i < amountOfImages; i++)
{
if(remainingTop >= 1080)
{
cropImage(1080, cumulitiveTop, i);
//printText(1080, cumulitiveTop, i);
}
else
{
cropImage(remainingTop, dimensions.height - remainingTop, i);
//printText(remainingTop, dimensions.height - remainingTop, i);
break;
}
remainingTop = remainingTop - 1080;
cumulitiveTop += 1080;
}
};
run();
I'm working on my own canvas drawer project, and just stuck in the zoom in/out function. In my project I'm using scale and translate to make the zoom, as I want to keep all the canvas and its elements in the center.
After sketching a little bit(not a math genius), I succeeded to draw out the following formula to use in the translate process, so the canvas will be kept in the middle of its view port after zooming: Old width and height / 2 - New width and height(which are old width and height multiply by scale step, which is 1.1 in my case) / 2.
Logically speaking, that should make it work. But after trying few times the zoom in and zoom out, I can clearly see that the canvas has a little offset and it's not being centered to the middle of the viewport(by viewport I mean the stroked square representing the canvas).
I took my code out of my project and put it in fiddle, right here:
https://jsfiddle.net/s82qambx/3/
index.html
<div id="letse-canvas-container">
<canvas id="letse-canvas" width="300px" height="300px"></canvas>
<canvas id="letse-upper-canvas" width="300px" height="300px"></canvas>
</div>
<div id="buttons">
<button id="zoomin">
Zoom-in
</button>
<button id="zoomout">
Zoom-out
</button>
</div>
main.js
const canvas = {
canvas: document.getElementById('letse-canvas'),
upperCanvas: document.getElementById('letse-upper-canvas')
};
canvas.canvas.ctx = canvas.canvas.getContext('2d');
canvas.upperCanvas.ctx = canvas.upperCanvas.getContext('2d');
const CANVAS_STATE = {
canvas: {
zoom: 1,
width: 300,
height: 300
}
}
const Elements = [
{
x: 20,
y: 20,
width: 30,
height: 40
},
{
x:170,
y:30,
width: 100,
height: 100
}
];
const button = {
zoomin: document.getElementById('zoomin'),
zoomout: document.getElementById('zoomout')
}
button.zoomin.addEventListener('click', (e) => {
canvasZoomIn(e, canvas);
});
button.zoomout.addEventListener('click', (e) => {
canvasZoomOut(e, canvas);
});
function canvasZoomIn(e, canvas) {
const zoomData = getZoomData('in');
canvas.upperCanvas.ctx.scale(zoomData.zoomStep, zoomData.zoomStep);
canvas.upperCanvas.ctx.translate(zoomData.translateX, zoomData.translateY);
canvas.upperCanvas.ctx.clearRect(0, 0, 300, 300);
canvas.canvas.ctx.scale(zoomData.zoomStep, zoomData.zoomStep);
canvas.canvas.ctx.translate(zoomData.translateX, zoomData.translateY);
canvas.canvas.ctx.clearRect(0, 0, 300, 300);
Elements.forEach((element) => {
canvas.canvas.ctx.strokeRect(element.x, element.y, element.width, element.height);
});
CANVAS_STATE.canvas.zoom = zoomData.scale;
CANVAS_STATE.canvas.width = zoomData.docWidth;
CANVAS_STATE.canvas.height = zoomData.docHeight;
console.log(CANVAS_STATE.canvas.zoom, 'zoom');
console.log(CANVAS_STATE.canvas.width, 'width');
console.log(CANVAS_STATE.canvas.height, 'height');
canvas.canvas.ctx.strokeRect(0, 0, 300, 300);
canvas.canvas.ctx.beginPath();
canvas.canvas.ctx.moveTo(0, 150);
canvas.canvas.ctx.lineTo(300, 150);
canvas.canvas.ctx.stroke();
CANVAS_STATE.canvas.draggable = canvas.canvas.width < CANVAS_STATE.canvas.width || canvas.canvas.height < CANVAS_STATE.canvas.height;
}
function canvasZoomOut(e, canvas) {
const zoomData = getZoomData('out');
canvas.upperCanvas.ctx.scale(zoomData.zoomStep, zoomData.zoomStep);
canvas.upperCanvas.ctx.translate(zoomData.translateX, zoomData.translateY);
canvas.upperCanvas.ctx.clearRect(0, 0, canvas.canvas.width, canvas.canvas.height);
canvas.canvas.ctx.scale(zoomData.zoomStep, zoomData.zoomStep);
canvas.canvas.ctx.translate(zoomData.translateX, zoomData.translateY);
canvas.canvas.ctx.clearRect(0, 0, canvas.canvas.width, canvas.canvas.height);
Elements.forEach((element) => {
canvas.canvas.ctx.strokeRect(element.x, element.y, element.width, element.height);
});
CANVAS_STATE.canvas.zoom = zoomData.scale;
CANVAS_STATE.canvas.width = zoomData.docWidth;
CANVAS_STATE.canvas.height = zoomData.docHeight;
console.log(CANVAS_STATE.canvas.zoom, 'zoom');
console.log(CANVAS_STATE.canvas.width, 'width');
console.log(CANVAS_STATE.canvas.height, 'height');
canvas.canvas.ctx.strokeRect(0, 0, 300, 300);
canvas.canvas.ctx.beginPath();
canvas.canvas.ctx.moveTo(0, 150);
canvas.canvas.ctx.lineTo(300, 150);
canvas.canvas.ctx.stroke();
CANVAS_STATE.canvas.draggable = canvas.canvas.width < CANVAS_STATE.canvas.width || canvas.canvas.height < CANVAS_STATE.canvas.height;
}
function getZoomData(zoom) {
const zoomStep = zoom === 'in' ? 1.1 : 1 / 1.1;
const scale = CANVAS_STATE.canvas.zoom * zoomStep;
const docWidth = CANVAS_STATE.canvas.width * zoomStep;
const docHeight = CANVAS_STATE.canvas.height * zoomStep;
const translateX = CANVAS_STATE.canvas.width / 2 - docWidth / 2;
const translateY = CANVAS_STATE.canvas.height / 2 - docHeight / 2;
console.log(zoomStep);
console.log(scale, 'check');
console.log(docWidth);
console.log(docHeight);
console.log(translateX, 'check');
console.log(translateY, 'check');
return {
zoomStep,
scale,
docWidth,
docHeight,
translateX,
translateY
};
}
main.css
#letse-canvas-container {
position: relative;
float: left;
}
#letse-canvas {
border: 1px solid rgb(0, 0, 0);
/* visibility: hidden; */
}
#letse-upper-canvas {
/* position: absolute; */
/* top: 0px; */
left: 0px;
border: 1px solid;
/* visibility: hidden; */
}
Can someone suggest a reason? What am I missing here?
OK! So I managed to derive the right formula after searching in the net and testing few options. I used:
function getZoomData(zoom) {
const zoomStep = zoom === 'in' ? 1.1 : 1 / 1.1;
const oldZoom = CANVAS_STATE.canvas.zoom;
const newZoom = oldZoom * zoomStep;
const zoomDifference = newZoom - oldZoom;
const docWidth = CANVAS_STATE.canvas.width * newZoom;
const docHeight = CANVAS_STATE.canvas.height * newZoom;
const translateX = (-(canvas.canvas.width / 2 * zoomDifference / newZoom));
const translateY = (-(canvas.canvas.height / 2 * zoomDifference / newZoom));